<?php
/***************************************************************************
*                            Orca Interactive Forum Script
*                              -----------------
*     begin                : Fr Nov 10 2006
*     copyright            : (C) 2006 BoonEx Group
*     website              : http://www.boonex.com/
* This file is part of Orca - Interactive Forum Script
*
* Orca is free software. This work is licensed under a Creative Commons Attribution 3.0 License. 
* http://creativecommons.org/licenses/by/3.0/
*
* Orca is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Creative Commons Attribution 3.0 License for more details. 
* You should have received a copy of the Creative Commons Attribution 3.0 License along with Orca, 
* see license.txt file; if not, write to marketing@boonex.com
***************************************************************************/


// forum operations

class Forum extends ThingPage
{	
    // mandatory methods
    var $getUserInfo; // $stringUser
    var $getUserPerm; // $stringUser, $stringType (public, provate, own), $stringAction (read, post, edit, del, search, sticky), $intForumId
    var $getLoginUser; // no parameters

    // optional methods
    var $onPostReply; // $arrayTopic, $stringPostText, $stringUser
    var $onPostEdit; // $arrayTopic, $intPostId, $stringPostText, $stringUser
    var $onPostDelete; // $arrayTopic, $intPostId, $stringUser
    var $onNewTopic; // $intForumId, $stringTopicSubject, $stringTopicText, $isTopicSticky, $stringUser, $stringTopicUri
    var $onVote; // $intPostId, $stringUser, $intVote (1 or -1)
    var $onReport; // $intPostId, $stringUser
    var $onFlag; // $intTopicId, $stringUser
    var $onUnflag; // $intTopicId, $stringUser

	/**
	 * constructor
	 */
	function Forum ()
    {
        $this->fdb = new DbForum ();
	}

	/**
	 * returns search results XML
	 * @param $text		search string
	 * @param $type		search type: msgs - messages | tlts - titles
	 * @param $forum	forum id to search within
	 * @param $u		search posts of this user only
	 * @param $disp		display: topics | posts
	 * @param $max_res	max number of results
	 */
	function getSearchResultsXML ($text, $type, $forum, $u, $disp, $max_res = 50)
	{				
		if (!$this->_checkUserPerm ('', '', 'search'))
		{
            return $this->_no_access();
		}

		switch ($type)
		{
			case 'msgs':
			case 'tlts':
				$a = $this->fdb->searchMessages (filter_to_db($text), filter_to_db($u), $forum, $type, ('posts' == $disp ? 1 : 0), $max_res);
				break;
			default:
				return '<error>[L[Wrong search type]]</error>';
		}

		$ws = preg_split("/\s+/", $text);	

		reset ($a);
		$s = '';		
		switch ($type)
		{
			case 'tlts':
				while ( list (,$r) = each ($a) )
				{
					// search hightlight
					
					
                    reset($ws);					
                    while (list (,$w) = each ($ws)) {
                        if ($w) {
                            $wreg = str_replace(array('(', ')'), array('\\(', '\\)'), $w);
                            $r['topic_title'] = preg_replace ("/($wreg)/i", "<span style=\"background-color:yellow\">$w</span>", $r['topic_title']);
                        }
                    }

					encode_post_text($r['cat_name']);
					encode_post_text($r['forum_title']);
					encode_post_text($r['topic_title'], 0, 1);					
					
					$s .= <<<EOF
					<sr date="{$r['date']}" user="{$r['user']}">
						<c id="{$r['cat_id']}" uri="{$r['cat_uri']}">{$r['cat_name']}</c>
						<f id="{$r['forum_id']}" uri="{$r['forum_uri']}">{$r['forum_title']}</f>
						<t id="{$r['topic_id']}" uri="{$r['topic_uri']}">{$r['topic_title']}</t>
					</sr>
EOF;
				}
				break;
			case 'msgs':
				while ( list (,$r) = each ($a) )
                {
                    
                    // search hightlight										
                    reset($ws);
                    while (list (,$w) = each ($ws))
                    {
                        if ($w)
                        {
                            $wreg = str_replace(array('(', ')'), array('\\(', '\\)'), $w);
                            $ind = eregi( "([^>]*<)", $r['post_text'], $ind ); // html tags?
                            if ($ind)
                                $r['post_text'] = preg_replace("/($wreg)(?=[^>]*<)/i", "<span style=\"background-color:yellow\">$w</span>", "<div>{$r['post_text']}</div>");
                            else
                                $r['post_text'] = preg_replace("/($wreg)/i", "<span style=\"background-color:yellow\">$w</span>", $r['post_text']);
                        }

                    }
                    
                    encode_post_text ($r['post_text']);
					
                    reset($ws);
                    while (list (,$w) = each ($ws)) {
                        $wreg = str_replace(array('(', ')'), array('\\(', '\\)'), $w);
                        $r['topic_title'] = preg_replace ("/($wreg)/i", "<span style=\"background-color:yellow\">$w</span>", $r['topic_title']);
                    }

					encode_post_text($r['cat_name']);
					encode_post_text($r['forum_title']);
					encode_post_text($r['topic_title'], 0, 1);					
					
					$s .= <<<EOF
					<sr date="{$r['date']}" user="{$r['user']}">
						<c id="{$r['cat_id']}" uri="{$r['cat_uri']}">{$r['cat_name']}</c>
						<f id="{$r['forum_id']}" uri="{$r['forum_uri']}">{$r['forum_title']}</f>
						<t id="{$r['topic_id']}" uri="{$r['topic_uri']}">{$r['topic_title']}</t>
						<p id="{$r['post_id']}">{$r['post_text']}</p>
					</sr>
EOF;
				}
				break;
		}

		$cu = $this->getUrlsXml ();
		encode_post_text($text, 0, 1);		
		return "<root>$cu<search><search_text>$text</search_text>$s</search></root>";
	}

	/**
	 * returns search  page XML
	 */
	function getSearchXML ($wp = false)
	{
		if (!$this->_checkUserPerm ('', '', 'search'))
		{
            return $this->_no_access($wp);
		}

		$a = $this->fdb->getCategs();
		reset ($a);
		$c = '';
		while ( list (,$r) = each ($a) )
		{			
			$c .= "<categ id=\"{$r['cat_id']}\" uri=\"{$r['cat_uri']}\">";
			encode_post_text($r['cat_name'], 0);
			$c .= "<title>{$r['cat_name']}</title>";
			$c .= '<forums>' . $this->getForumsShortXML ($r['cat_id'], 0) . '</forums>';
			$c .= "</categ>";
		}
		
		$s = "<categs>$c</categs>";

        $cu = $this->getUrlsXml ();

		if ($wp)
		{
			$this->setTitle ('<![CDATA[[L[Search The Forum]]]]>');
			$li = $this->_getLoginInfo ($u);
			return $this->addHeaderFooter ($li, "<search>$s</search>");
		}
		else
        {
            return "<root>$cu<search>$s</search></root>";
        }
	}

	/**
	 * returns new topic page XML
	 */
	function getNewTopicXML ($forum_uri)
	{
        $f = $this->fdb->getForumByUri (filter_to_db($forum_uri));
        $forum_id = $f['forum_id'];

		if (!$this->_checkUserPerm ('', $f['forum_type'], 'post', $forum_id))
		{
            return $this->_no_access();
		}        

		$sticky = 0;
		if ($this->_checkUserPerm ('', '', 'sticky', $forum_id))
		{
			$sticky = 1;
		}        

		encode_post_text ($f['forum_title'], 0);
		encode_post_text ($f['forum_desc'], 0);
		
		$x1 = <<<EOF
<forum>
    <id>{$f['forum_id']}</id>
    <uri>{$f['forum_uri']}</uri>
	<title>{$f['forum_title']}</title>
	<desc>{$f['forum_desc']}</desc>
	<type>{$f['forum_type']}</type>
</forum>
EOF;

		$cat = $this->fdb->getCat ($f['cat_id']);
		encode_post_text ($cat['cat_name'], 0);
		$x2 = <<<EOF
<cat>
    <id>{$f['cat_id']}</id>
    <uri>{$cat['cat_uri']}</uri>
	<title>{$cat['cat_name']}</title>
</cat>
EOF;

        $cu = $this->getUrlsXml ();

        $u = $this->_getLoginUser();
        $signature = $this->fdb->getSignature ($u);
        encode_post_text ($signature, 0);

		return "<root>$cu<new_topic sticky=\"$sticky\">{$x2}{$x1}<signature>{$signature}</signature></new_topic></root>";
	}

	/**
	 * returns post reply page XML
	 */
	function getPostReplyXML ($forum_id, $topic_id)
	{
        $f = $this->fdb->getForum ($forum_id);

        $t = $this->fdb->getTopic ((int)$topic_id);

		if (!$this->_checkUserPerm ('', $f['forum_type'], 'post', (int)$forum_id) || $t['topic_locked'])
		{
            return $this->_no_access();
		}

		encode_post_text ($f['forum_title'], 0);
		encode_post_text ($f['forum_desc'], 0);        
		
		$x1 = <<<EOF
<forum>
    <id>{$f['forum_id']}</id>
	<uri>{$f['forum_uri']}</uri>
	<title>{$f['forum_title']}</title>
	<desc>{$f['forum_desc']}</desc>
	<type>{$f['forum_type']}</type>    
</forum>
EOF;

        $u = $this->_getLoginUser();
        $signature = $this->fdb->getSignature ($u);
        encode_post_text ($signature, 0);

		$cu = $this->getUrlsXml ();
		return "<root>$cu<new_topic>$x1<topic><id>$topic_id</id></topic><signature>{$signature}</signature></new_topic></root>";
	}

	/**
	 * returns single post XML
	 * @param $post_id		post id
	 * @param $force_show	force show hidden post
	 */	
	function getHiddenPostXML ($post_id, $force_show)
	{
		global $gConf;
		
		$post_id = (int)$post_id;
		if (!$post_id) return false;
		
		$ui = array ();

        $t = $this->fdb->getTopicByPostId ($post_id);
        $topic_id = $t['topic_id'];

        $f = $this->fdb->getForum ($t['forum_id']);        
        $forum_id = $f['forum_id'];
		
		// check user permission to read this topic posts

        $forum_type = $f['forum_type'];

		if (!$this->_checkUserPerm ('', $forum_type, 'read', $forum_id))
		{
            return $this->_no_access();
		}

		// check user permissions to delete or edit posts
		$gl_allow_edit = 0;
		$gl_allow_del = 0;
        $gl_allow_hide_posts = 0;
        $gl_allow_unhide_posts = 0;
        $gl_allow_download = 0;
		
		if ($this->_checkUserPerm ('', $forum_type, 'edit', $forum_id))
			$gl_allow_edit = 1;

		if ($this->_checkUserPerm ('', $forum_type, 'del', $forum_id))
			$gl_allow_del = 1;

		if ($this->_checkUserPerm ('', '', 'hide_posts', $forum_id))
			$gl_allow_hide_posts = 1;

		if ($this->_checkUserPerm ('', '', 'unhide_posts', $forum_id))
			$gl_allow_unhide_posts = 1;

		if ($this->_checkUserPerm ('', '', 'download', $forum_id));
            $gl_allow_download = 1;

		$u = $this->_getLoginUser();
		
		$r = $this->fdb->getPost($post_id, $u);
		
		// acquire user info
		if (!$ui[$r['user']])
		{				
			$aa = $this->_getUserInfo ($r['user']);
            encode_post_text ($aa['role']);
			$ui[$r['user']] = array ('posts' => (int)$this->fdb->getUserPosts($r['user']), 'avatar' => $aa['avatar'], 'url' => $aa['profile_url'], 'onclick' => $aa['profile_onclick'], 'role' => $aa['role']);
		}

		$allow_edit = $gl_allow_edit;
		$allow_del = $gl_allow_del;

		if (!$allow_edit && $r['user'] == $this->_getLoginUserName())
		{
			if ($this->_checkUserPerm ($r['user'], 'own', 'edit', $forum_id) && !$this->_isEditTimeout($r['post_id']))
				$allow_edit = 1;
		}			

		if (!$allow_del && $r['user'] == $this->_getLoginUserName())
		{
			if ($this->_checkUserPerm ($r['user'], 'own', 'del', $forum_id) && !$this->_isEditTimeout($r['post_id']))
				$allow_del = 1;
		}			

		$cu = $this->getUrlsXml ();
        $li = "<logininfo>" . array2xml($this->_getLoginInfo ($u)) . "</logininfo>";

        $files = $this->_getAttachmentsXML ($r['post_id']);

        encode_post_text ($r['post_text']);

		return <<<EOF
<root>
$cu
$li
<forum>
    <id>{$f['forum_id']}</id>
    <uri>{$f['forum_uri']}</uri>
</forum>
<topic>
    <id>$topic_id</id>
    <uri>{$t['topic_uri']}</uri>
</topic>
<post id="{$r['post_id']}" force_show="$force_show">
	<text>{$r['post_text']}</text>
	<when>{$r['when']}</when>
	<allow_edit>$allow_edit</allow_edit>
	<allow_del>$allow_del</allow_del>
	<allow_hide_posts>$gl_allow_hide_posts</allow_hide_posts>
	<allow_unhide_posts>$gl_allow_unhide_posts</allow_unhide_posts>
    <allow_download>$gl_allow_download</allow_download>
	<points>{$r['votes']}</points>
    <hidden>{$r['hidden']}</hidden>
	<vote_user_point>{$r['vote_user_point']}</vote_user_point>	
	<user posts="{$ui[$r['user']]['posts']}" name="{$r['user']}">
		<avatar>{$ui[$r['user']]['avatar']}</avatar>
		<url>{$ui[$r['user']]['url']}</url>
		<onclick>{$ui[$r['user']]['onclick']}</onclick>
        <role>{$ui[$r['user']]['role']}</role>
	</user>
    <attachments>$files</attachments>
	<min_point>{$gConf['min_point']}</min_point>
</post>
</root>
EOF;
	}
	
	/**
	 * returns topic posts XML
	 * @param $topic_id
	 * @param $wp			return whole page XML
	 */
	function getPostsXML ($topic_uri, $wp)
	{
		global $gConf;
		
		$ui = array ();

        $u = $this->_getLoginUser();
        $a = $this->fdb->getPostsByUri(filter_to_db($topic_uri), $u);
        $topic_id = $a[0]['topic_id'];

		// check user permission to read this topic posts
        $f = $this->fdb->getForum ($a[0]['forum_id']);
        $forum_id = $f['forum_id'];
        $forum_type = $f['forum_type'];

		if (!$this->_checkUserPerm ($u, $forum_type, 'read', $forum_id))
		{
            return $this->_no_access($wp);
		}

        $t = $this->fdb->getTopic ($topic_id);

        $gl_allow_hide_topics = 0;
        $gl_allow_unhide_topics = 0;        

		if ($this->_checkUserPerm ($u, '', 'hide_topics', $forum_id))
			$gl_allow_hide_topics = 1;

		if ($this->_checkUserPerm ($u, '', 'unhide_topics', $forum_id))
			$gl_allow_unhide_topics = 1;

        if (!$gl_allow_unhide_topics && !$gl_allow_hide_topics && $t['topic_hidden'])
            return $this->_no_access($wp);

		$this->setTrackTopic ($topic_id);

		// check user permissions to delete or edit posts
		$gl_allow_edit = 0;
		$gl_allow_del = 0;
		$gl_allow_hide_posts = 0;
		$gl_allow_unhide_posts = 0;

        $gl_allow_move_topics = 0;
        $gl_allow_del_topics = 0;
        $gl_allow_download = 0;
		
		if ($this->_checkUserPerm ($u, $forum_type, 'edit', $forum_id))
			$gl_allow_edit = 1;

		if ($this->_checkUserPerm ($u, $forum_type, 'del', $forum_id))
			$gl_allow_del = 1;

		if ($this->_checkUserPerm ($u, '', 'hide_posts', $forum_id))
			$gl_allow_hide_posts = 1;
			
		if ($this->_checkUserPerm ('', '', 'unhide_posts', $forum_id))
			$gl_allow_unhide_posts = 1;
	
		if ($this->_checkUserPerm ($u, '', 'move_topics', $forum_id))
			$gl_allow_move_topics = 1;

		if ($this->_checkUserPerm ($u, '', 'del_topics', $forum_id))
			$gl_allow_del_topics = 1;

		if ($this->_checkUserPerm ('', '', 'download', $forum_id));
            $gl_allow_download = 1;

		reset ($a);
		$p = '';
		while ( list (,$r) = each ($a) )
		{
			
			// acquire user info
			if (!$ui[$r['user']])
			{				
				$aa = $this->_getUserInfo ($r['user']);
                encode_post_text ($aa['role']);

                $signature = $this->fdb->getSignature ($r['user']);
                encode_post_text ($signature, 0);

				$ui[$r['user']] = array ('posts' => $this->fdb->getUserPosts($r['user']), 'avatar' => $aa['avatar'], 'url' => $aa['profile_url'], 'onclick' => $aa['profile_onclick'], 'role' => $aa['role'], 'signature' => $signature);
			}

			$allow_edit = $gl_allow_edit;
			$allow_del = $gl_allow_del;

			if (!$allow_edit && $r['user'] == $u)
			{
				if ($this->_checkUserPerm ($r['user'], 'own', 'edit', $forum_id) && !$this->_isEditTimeout($r['post_id']))
					$allow_edit = 1;
			}			

			if (!$allow_del && $r['user'] == $u)
			{
				if ($this->_checkUserPerm ($r['user'], 'own', 'del', $forum_id) && !$this->_isEditTimeout($r['post_id']))
					$allow_del = 1;
			}			
		
            $files = $this->_getAttachmentsXML ($r['post_id']);

            encode_post_text ($r['post_text'], $wp, 1);			
            
			$p .= <<<EOF
<post id="{$r['post_id']}"  force_show="0">
	<text>{$r['post_text']}</text>
	<when>{$r['when']}</when>
	<allow_edit>$allow_edit</allow_edit>
	<allow_del>$allow_del</allow_del>
	<allow_hide_posts>$gl_allow_hide_posts</allow_hide_posts>
	<allow_unhide_posts>$gl_allow_unhide_posts</allow_unhide_posts>
    <allow_download>$gl_allow_download</allow_download>
	<points>{$r['votes']}</points>
    <hidden>{$r['hidden']}</hidden>
	<vote_user_point>{$r['vote_user_point']}</vote_user_point>	
	<user posts="{$ui[$r['user']]['posts']}" name="{$r['user']}">
		<avatar>{$ui[$r['user']]['avatar']}</avatar>
		<url>{$ui[$r['user']]['url']}</url>
		<onclick>{$ui[$r['user']]['onclick']}</onclick>
        <role>{$ui[$r['user']]['role']}</role>
        <signature>{$ui[$r['user']]['signature']}</signature>
	</user>
    <attachments>$files</attachments>
	<min_point>{$gConf['min_point']}</min_point>
</post>
EOF;
			$rr = $r;
		}

        $cat = $this->fdb->getCat ($f['cat_id']);
		encode_post_text ($cat['cat_name'], $wp);
		$x0 = <<<EOF
<cat>
    <id>{$cat['cat_id']}</id>
    <uri>{$cat['cat_uri']}</uri>
	<title>{$cat['cat_name']}</title>
</cat>
EOF;

		encode_post_text ($t['forum_title'], $wp);
		encode_post_text ($t['forum_desc'], $wp);
		$x1 = <<<EOF
<forum>
    <id>{$f['forum_id']}</id>
    <uri>{$f['forum_uri']}</uri>
	<title>{$t['forum_title']}</title>
	<desc>{$t['forum_desc']}</desc>
	<type>{$f['forum_type']}</type>
</forum>
EOF;
		encode_post_text ($t['topic_title'], $wp, 1);
        $topic_flagged = $this->fdb->isFlagged ($rr['topic_id'], $u) ? 1 : 0;
		$x2 = <<<EOF
<topic>
    <id>{$t['topic_id']}</id>
	<uri>{$t['topic_uri']}</uri>
    <title>{$t['topic_title']}</title>
    <locked>{$t['topic_locked']}</locked>
    <hidden>{$t['topic_hidden']}</hidden>
    <flagged>{$topic_flagged}</flagged>
	<allow_hide_topics>$gl_allow_hide_topics</allow_hide_topics>
	<allow_unhide_topics>$gl_allow_unhide_topics</allow_unhide_topics>
	<allow_move_topics>$gl_allow_move_topics</allow_move_topics>
	<allow_del_topics>$gl_allow_del_topics</allow_del_topics>
</topic>
EOF;

		if ($wp)
		{
			$this->setTitle($t['topic_title']);
			$li = $this->_getLoginInfo ($u);
			return $this->addHeaderFooter ($li, "<posts>{$x0}{$x1}{$x2}{$p}</posts>");
		}
		else
		{
            $cu = $this->getUrlsXml ();
            $li = $this->_getLoginInfo ($u);
			return "<root><logininfo>" . array2xml($li) . "</logininfo>$cu<posts>{$x0}{$x1}{$x2}{$p}</posts></root>";
		}
	}


	/**
	 * returns my threads topics XML
	 * @param $wp			return whole page XML
	 */
	function getXxxTopicsXML ($wp, $sTitle, $sDesc, $sFunc)
	{
		global $gConf;

		$user = $this->getLoginUser();		

		if (!$user)
		{
            return $this->_no_access($wp);
		}

		$x1 = <<<EOF
<forum>
	<title><![CDATA[$sTitle]]></title>
	<desc><![CDATA[$sDesc]]></desc>
</forum>
EOF;

		$x2 = '';
		
		$user_last_act = (int)$this->fdb->getUserLastActivity ($user);

		$a = $this->fdb->$sFunc($user);
		reset ($a);
		$t = '';
		while ( list (,$r) = each ($a) )
		{
				$lp = $this->fdb->getTopicPost($r['topic_id'], 'last');
				$fp = $this->fdb->getTopicPost($r['topic_id'], 'first');
				
				$td = $this->fdb->getTopicDesc ($r['topic_id']);
                $td = orca_mb_substr($td, 0, $gConf['topics_desc_len']);
                if (orca_mb_len($td) == $gConf['topics_desc_len']) 
                    $td .= '...';
				$this->_buld_topic_desc ($td);

				if (!$user)
					$new_topic = 0;
				else
					$new_topic = $this->isNewTopic ($r['topic_id'],  $r['last_post_when'], $user_last_act) ? 1 : 0;

				encode_post_text ($r['topic_title'], $wp, 1);				
									
				$t .= <<<EOF
<topic id="{$r['topic_id']}" new="$new_topic" lpt="{$r['last_post_when']}" lut="{$user_last_act}">
    <uri>{$r['topic_uri']}</uri>
	<title>{$r['topic_title']}</title>
	<desc>{$td}</desc>
	<count>{$r['count_posts']}</count>
	<last_u>{$lp['user']}</last_u>
	<last_d>{$lp['when']}</last_d>
	<first_u>{$fp['user']}</first_u>
	<first_d>{$fp['when']}</first_d>
</topic>
EOF;
		}	

		if ($wp)
		{
			$this->setTitle($sTitle);
			$li = $this->_getLoginInfo ();
			return $this->addHeaderFooter ($li, "<topics>{$x2}{$x1}{$t}</topics>");
		}
		else
		{
			$cu = $this->getUrlsXml ();
			return "<root>$cu<topics>{$x2}{$x1}{$t}</topics></root>";
		}
	}

	/**
	 * returns my threads topics XML
	 * @param $wp			return whole page XML
	 */
	function getHiddenTopicsXML ($wp)
	{
        return $this->getXxxTopicsXML ($wp, '[L[Hidden Topics]]', '[L[Hidden topics]]', 'getHiddenTopics');
	}

	/**
	 * returns my threads topics XML
	 * @param $wp			return whole page XML
	 */
	function getMyThreadsXML ($wp)
	{
        return $this->getXxxTopicsXML ($wp, '[L[My Topics]]', '[L[Topics you participate in]]', 'getMyThreadsTopics');
	}
	
	/**
	 * returns flagged topics XML
	 * @param $wp			return whole page XML
	 */
	function getMyFlagsXML ($wp)
	{
        return $this->getXxxTopicsXML ($wp, '[L[Flagged topics]]', '[L[Topics you have flagged]]', 'getMyFlaggedTopics');
	}

	/**
	 * returns forum topics XML
	 * @param $forum_id		forum id
	 * @param $wp			return whole page XML
	 * @param $start		record to start with
	 */
	function getTopicsXML ($forum_uri, $wp, $start = 0)
	{
		global $gConf;

        $f = $this->fdb->getForumByUri (filter_to_db($forum_uri));
        $forum_id = $f['forum_id'];

		$user = $this->getLoginUser();

		if (!$this->_checkUserPerm ($user, $f['forum_type'], 'read', $forum_id))
        {
            return $this->_no_access($wp);
		}

		encode_post_text ($f['forum_title'], $wp);
		encode_post_text ($f['forum_desc'], $wp);
		
		$x1 = <<<EOF
<forum>
    <id>{$f['forum_id']}</id>
	<uri>{$f['forum_uri']}</uri>
	<title>{$f['forum_title']}</title>
	<desc>{$f['forum_desc']}</desc>
	<type>{$f['forum_type']}</type>
</forum>
EOF;

		$cat = $this->fdb->getCat ($f['cat_id']);
		encode_post_text ($cat['cat_name'], $wp);
		$x2 = <<<EOF
<cat>
    <id>{$cat['cat_id']}</id>
    <uri>{$cat['cat_uri']}</uri>
	<title>{$cat['cat_name']}</title>
</cat>
EOF;

		$user_last_act = (int)$this->fdb->getUserLastActivity ($user);

		$a = $this->fdb->getTopics($forum_id, $start);
		reset ($a);
		$t = '';
		while ( list (,$r) = each ($a) )
		{
				$td = $this->fdb->getTopicDesc ($r['topic_id']);
                $td = orca_mb_substr($td, 0, $gConf['topics_desc_len']);
                if (orca_mb_len($td) == $gConf['topics_desc_len']) 
                    $td .= '...';
				$this->_buld_topic_desc ($td);

				if (!$user)
					$new_topic = 0;
				else
					$new_topic = $this->isNewTopic ($r['topic_id'],  $r['last_post_when'], $user_last_act) ? 1 : 0;

									
				encode_post_text ($r['topic_title'], $wp, 1);				
				
				$t .= <<<EOF
<topic id="{$r['topic_id']}" new="$new_topic" lpt="{$r['last_post_when']}" lut="{$user_last_act}" sticky="{$r['topic_sticky']}" locked="{$r['topic_locked']}">
	<uri>{$r['topic_uri']}</uri>
	<title>{$r['topic_title']}</title>
	<desc>{$td}</desc>
	<count>{$r['count_posts']}</count>
	<last_u>{$r['last_post_user']}</last_u>
	<last_d>{$r['last_when']}</last_d>
	<first_u>{$r['first_post_user']}</first_u>
	<first_d>{$r['first_when']}</first_d>
</topic>
EOF;
		}

		$p = '';
		$num = $this->fdb->getTopicsNum($forum_id);
		for ($i = 0 ; $i < $num ; $i += $gConf['topics_per_page'])
            $p .= '<p c="' . (($start >= $i && $start < ($i + $gConf['topics_per_page'])) ? 1 : 0 ). '" start="' . $i . '">' . ($i/$gConf['topics_per_page'] + 1) . '</p>';
	

		if ($wp)
		{
			$this->setTitle($f['forum_title']);
			$li = $this->_getLoginInfo ($user);
			return $this->addHeaderFooter ($li, "<topics><pages num=\"$num\" per_page=\"{$gConf['topics_per_page']}\">$p</pages>{$x2}{$x1}{$t}</topics>");
		}
		else
		{
			$cu = $this->getUrlsXml ();
			return "<root>$cu<topics><pages num=\"$num\" per_page=\"{$gConf['topics_per_page']}\">$p</pages>{$x2}{$x1}{$t}</topics></root>";
		}
	}

	/**
	 * returns recent topics XML
	 * @param $forum_id		forum id
	 * @param $wp			return whole page XML
	 * @param $start		record to start with
	 */
	function getRecentTopicsXML ($wp, $start = 0)
	{
		global $gConf;

		$user = $this->getLoginUser();

		$user_last_act = (int)$this->fdb->getUserLastActivity ($user);

		$a = $this->fdb->getRecentTopics($start);
		reset ($a);
		$t = '';
		while ( list (,$r) = each ($a) )
		{
				if (!$user)
					$new_topic = 0;
				else
					$new_topic = $this->isNewTopic ($r['topic_id'],  $r['last_post_when'], $user_last_act) ? 1 : 0;
									
				encode_post_text ($r['topic_title'], $wp, 1);
                encode_post_text ($r['forum_title'], $wp, 1);
                encode_post_text ($r['cat_name'], $wp, 1);
				
				$t .= <<<EOF
<topic id="{$r['topic_id']}" new="$new_topic" lpt="{$r['last_post_when']}" lut="{$user_last_act}" sticky="{$r['topic_sticky']}" locked="{$r['topic_locked']}">
	<uri>{$r['topic_uri']}</uri>
	<title>{$r['topic_title']}</title>
	<desc />
	<count>{$r['count_posts']}</count>
	<last_u>{$r['last_post_user']}</last_u>
	<last_d>{$r['last_when']}</last_d>
	<first_u>{$r['first_post_user']}</first_u>
	<first_d>{$r['first_when']}</first_d>
    <forum id="{$r['forum_id']}" uri="{$r['forum_uri']}">{$r['forum_title']}</forum>
    <cat id="{$r['cat_id']}" uri="{$r['cat_uri']}">{$r['cat_name']}</cat>
</topic>
EOF;
		}

		$p = '';
		$num = $this->fdb->getRecentTopicsNum();
		for ($i = 0 ; $i < $num ; $i += $gConf['topics_per_page'])
            $p .= '<p c="' . (($start >= $i && $start < ($i + $gConf['topics_per_page'])) ? 1 : 0 ). '" start="' . $i . '">' . ($i/$gConf['topics_per_page'] + 1) . '</p>';
	

		if ($wp)
		{
            $this->setTitle('[L[Recent Topics]]');
			$li = $this->_getLoginInfo ($user);
			return $this->addHeaderFooter ($li, "<topics><pages num=\"$num\" per_page=\"{$gConf['topics_per_page']}\">$p</pages>{$t}</topics>");
		}
		else
		{
			$cu = $this->getUrlsXml ();
			return "<root>$cu<topics><pages num=\"$num\" per_page=\"{$gConf['topics_per_page']}\">$p</pages>{$t}</topics></root>";
		}
	}

	/**
	 * returns array with viewed topics
	 */ 
	function getTrackTopics ()
	{
		$a = unserialize($_COOKIE['track_topics']);
		if (!is_array($a)) return array ();
		return $a;
	}

	/**
	 * mark topic as viewed
	 */ 
	function setTrackTopic ($topic_id)
	{
		$a = unserialize($_COOKIE['track_topics']);
		if (!is_array($a)) $a = array ();
		$a[$topic_id] = time();
		setcookie ('track_topics', serialize($a));
	}

	/**
	 * detect new topic by last topic update time and user activity and cookies
	 *
	 */ 
	function isNewTopic ($topic_id, $topic_last_time, $user_last_time)
	{
		$a = $this->getTrackTopics ();		
		
		if ($a[$topic_id] && $topic_last_time > $a[$topic_id]) 
			return 1;
		else if ($a[$topic_id])
			return 0;

		if (!$user_last_time) return 1;

		if ($topic_last_time > $user_last_time) return 1;

		return 0;
	}

	/**
	 * returns forums XML
	 */
	function getForumsShortXML ($cat, $root)
	{
		if ($root) 
			$c = '<forums>';
		else
			$c = '';
		$aa = $this->fdb->getForums ($cat);
		reset ($aa);
		while ( list (,$rr) = each ($aa) )
		{
			encode_post_text($rr['forum_title'], 0);
			
			$c .= <<<EOF
<forum id="{$rr['forum_id']}">
    <uri>{$rr['forum_uri']}</uri>
	<title>{$rr['forum_title']}</title>
	<type>{$rr['forum_type']}</type>
</forum>

EOF;
		}
		if ($root)
			return $c."</forums>\n";
		else
			return $c;
	}

	/**
	 * returns forums XML
	 */
	function getForumsXML ($cat, $root)
    {
		if ($root) 
			$c = '<forums>';
		else
            $c = '';
        $aa = $this->fdb->getForumsByCatUri (filter_to_db($cat));

		reset ($aa);
		while ( list (,$rr) = each ($aa) )
		{
			encode_post_text ($rr['forum_title'], $root);
			encode_post_text ($rr['forum_desc'], $root);

            if (!$rr['forum_last_ts'])
                $rr['forum_last'] = '';

			$c .= <<<EOF
<forum id="{$rr['forum_id']}" new="0" cat="$cat">
    <uri>{$rr['forum_uri']}</uri>
	<title>{$rr['forum_title']}</title>
	<desc>{$rr['forum_desc']}</desc>
	<type>{$rr['forum_type']}</type>
	<posts>{$rr['forum_posts']}</posts>
	<topics>{$rr['forum_topics']}</topics>
	<last>{$rr['forum_last']}</last>
</forum>

EOF;
		}
		
		if ($root)
		{
			$cu = $this->getUrlsXml ();
			return '<root>' . $cu . $c . "</forums></root>\n";
		}
		else
		{
			return $c;
		}
	}



	/**
	 * returns page XML
	 */
	function getPageXML ($first_load = 1, &$p)
	{
		global $gConf;

		if (isset($p['manage_forum']) && $p['manage_forum'] && !$this->isAdmin())
		{
            return $this->_no_access($first_load);
		}

		$a = $this->fdb->getCategs();
		reset ($a);
        $c = '';
		while ( list (,$r) = each ($a) )
		{
			$icon_url  = $r['cat_icon'] ? $gConf['url']['icon'] . $r['cat_icon'] : '';
			$c .= "<categ id=\"{$r['cat_id']}\" uri=\"{$r['cat_uri']}\" icon=\"$icon_url\" count_posts=\"{$r['count_posts']}\" count_topics=\"{$r['count_topics']}\" count_forums=\"{$r['count_forums']}\">";
			encode_post_text ($r['cat_name'], 1);
            $c .= "<title>{$r['cat_name']}</title>";

            if ((isset($p['cat']) && $p['cat'] == $r['cat_uri']) || ($r['cat_expanded'] && !isset($p['cat'])))
            {
				$this->setTitle ($r['cat_name']);
				$c .= '<forums>'.$this->getForumsXML ($r['cat_uri'], 0) . '</forums>';
			}
			$c .= "</categ>";
		}
		
		$s = "<categs>$c</categs>";

		if ($first_load && isset($p['action']) && 'goto' == $p['action'] && isset($p['forum_id']))
		{
			$s .= "<onload>f.selectForum('" . $p['forum_id'] . "', 0)</onload>";
		}
		
		if ($first_load && isset($p['action']) && 'goto' == $p['action'] && isset($p['topic_id']))
		{
			$s .= "<onload>f.selectTopic('" . $p['topic_id'] . "')</onload>";
		}

		// live tracker
		$lt = '';//"<live_tracker>" . $this->getLivePostsXML() . "</live_tracker>";
		
		$li = $this->_getLoginInfo ();        

		if ($first_load)
		{
			return $this->addHeaderFooter ($li, $s.$lt);
		}
		else
		{
			$cu = $this->getUrlsXml ();
			return "<root>$cu<logininfo>".array2xml($li)."</logininfo><page>{$s}{$lt}</page></root>";
		}
	}

    function liveTracker () {
        $lt = "<live_tracker>" . $this->getLivePostsXML() . "</live_tracker>";        
        return $this->addHeaderFooter ($li, $lt);
    }

	function getLivePostsXML ($count = 10, $ts = 0)
    {
		global $gConf;

		$ret = '';

		if ($ts < (time() - 172800))
			$ts = 0;

		$a = $this->fdb->getLivePosts ($count, $ts);
        reset ($a);
        $ui = array ();
		while (list(,$r) = each ($a))
        {
            // acquire user info
            if (!isset($ui[$r['user']]))
            {
                $aa = $this->_getUserInfo ($r['user']);
                encode_post_text ($aa['role']);
                $ui[$r['user']] = array ('avatar' => $aa['avatar'], 'url' => $aa['profile_url'], 'onclick' => $aa['profile_onclick'], 'role' => $aa['role']);
            }

            $this->_buld_topic_desc ($r['post_text']);

            encode_post_text($r['topic_title'], 0, 1);
            encode_post_text($r['forum_title'], 0);
            encode_post_text($r['cat_name'], 0);                        
            
            $r['when'] = $this->_format_when ($r['sec']);

			$ret .= <<<EOF
<post id="{$r['post_id']}" ts="{$r['ts']}">
	<text>{$r['post_text']}</text>
	<user>{$r['user']}</user>
    <date>{$r['when']}</date>

    <avatar>{$ui[$r['user']]['avatar']}</avatar>
    <profile>{$ui[$r['user']]['url']}</profile>
    <onclick>{$ui[$r['user']]['onclick']}</onclick>
    <role>{$ui[$r['user']]['role']}</role>

	<topic id="{$r['topic_id']}" uri="{$r['topic_uri']}">{$r['topic_title']}</topic>
	<forum id="{$r['forum_id']}" uri="{$r['forum_uri']}">{$r['forum_title']}</forum>
	<cat id="{$r['cat_id']}" uri="{$r['cat_uri']}">{$r['cat_name']}</cat>
	<base>{$gConf['url']['base']}</base>
</post>
EOF;
		}

		return $ret;
	}

	/**
	 * check if new posts are available
	 *	@param	$ts		timestamp of last post
	 */ 
	function isNewPost ($ts)
	{
		return '<ret>' . (int)$this->fdb->getNewPostTs ($ts) . '</ret>';
	}


	/**
	 * post reply
	 * @param $p	_post array
	 */
	function postReplyXML (&$p)
	{
		$f = $this->fdb->getForum ((int)$p['forum_id']);

        $t = $this->fdb->getTopic ((int)$p['topic_id']);

		if (!$this->_checkUserPerm ('', $f['forum_type'], 'post', (int)$p['forum_id']) || $t['topic_locked'] || bx_is_spam($p['topic_text'])) 
		{
			return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">
	window.parent.document.f.accessDenied();
</script>
</body>
</html>
EOF;
		}		


		// post mesage here

		$p['forum_id'] = (int)$p['forum_id'];
		$p['topic_id'] = (int)$p['topic_id'];

		$user = $this->_getLoginUserName ();

		prepare_to_db($p['topic_text'], 1);

		$iReplyId = $this->fdb->postReply ($p['forum_id'], $p['topic_id'], $p['topic_text'], $user);
		$aReply = $this->fdb->getPost($iReplyId, '');

        $this->_handleSignature ($_POST, $user);

        $isUploadSuccess = $this->_handleUpload ($p, $iReplyId);

        $t = $this->fdb->getTopic($p['topic_id']);

        if (is_callable($this->onPostReply))
            call_user_func_array ($this->onPostReply,  array($t, $aReply['post_text'], $user));

		return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">
    if (!$isUploadSuccess)
        window.parent.alert ('[L[Some or all files upload failed]]');
	window.parent.document.f.replySuccess('{$f['forum_uri']}', '{$t['topic_uri']}');
</script>
</body>
</html>
EOF;


	}

	/**
	 * xml for edit post 
	 * @param $post_id		post id
	 * @param $topic_id		topic id
	 */
	function editPostXml ($post_id, $topic_id)
	{
        $cu = $this->getUrlsXml ();
        if ($post_id)
        {
            $a = $this->fdb->getTopicByPostId ($post_id);
            $t = $this->fdb->getTopic ($a['topic_id']);
            $topic_id = $t['topic_uri'];

            $timeout = 'disabled';
            $access = 'deny';
            if ($this->_checkUserPerm ('', $t['forum_type'], 'edit', $t['forum_id']) )
                $access = 'allow';
            if ('deny' == $access
                && 
                ($user = $this->fdb->getPostUser((int)$post_id)) == $this->_getLoginUserName()
                && 
                $this->_checkUserPerm ('', 'own', 'edit', $t['forum_id']) ) {

                $when = $this->fdb->getPostWhen ((int)$post_id);
                $timeout = $GLOBALS['gConf']['edit_timeout'] - (time() - $when);                    
                $access = $timeout > 10 ? 'allow' : 'deny';
            }
        }

        $files = '';
        $signature = '';
        if ('allow' == $access) {
            $files = $this->_getAttachmentsXML ((int)$post_id);
            $signature = $this->fdb->getSignature ($this->fdb->getPostUser((int)$post_id));
            encode_post_text ($signature, 0);
        }

		return <<<EOS
<root>
	$cu
	<edit_post>
		<post_id>$post_id</post_id>
        <topic_id>$topic_id</topic_id>
        <timeout>$timeout</timeout>
        <access>$access</access>
        <attachments>$files</attachments>
        <signature>$signature</signature>
	</edit_post>
</root>
EOS;
	}

	/**
	 * edit post
	 * @param $post_id		post id
	 * @param $topic_id		topic id
	 * @param $text			new post text
	 */
	function editPost ($post_id, $topic_id, $text)
	{
		$no_access = true;

        $t = $this->fdb->getTopicByUri (filter_to_db($topic_id));        
        $user = $this->fdb->getPostUser((int)$post_id);

		if ($this->_checkUserPerm ('', $t['forum_type'], 'edit', $t['forum_id'])) 
			$no_access = false;
		if ($no_access && $user == $this->_getLoginUserName())
			if ($this->_checkUserPerm ('', 'own', 'edit', $t['forum_id']) && !$this->_isEditTimeout((int)$post_id)) 
				$no_access = false;
        
		if ($no_access)
		{
			return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">
	window.parent.document.f.accessDenied();
</script>
</body>
</html>
EOF;
		}	

		// edit post here		
		prepare_to_db($text, 1);
		
		$this->fdb->editPost ($post_id, $text, $user);        

        $this->_handleSignature ($_POST, $user);

        $isUploadSuccess = $this->_handleUpload ($_POST, $post_id);

        if (is_callable($this->onPostEdit))
            call_user_func_array ($this->onPostEdit,  array($t, $post_id, $text, $user));

		return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">
    if (!$isUploadSuccess)
        window.parent.alert ('[L[Some or all files upload failed]]');
	window.parent.document.f.editSuccess('{$t['topic_uri']}');
</script>
</body>
</html>
EOF;

	}

	/**
	 * delete post
	 * @param $post_id		post id
	 * @param $topic_id		topic id
	 * @param $forum_id		forum id 
	 */
	function deletePostXML ($post_id, $topic_id, $forum_id)
	{
		$no_access = true;

        $f = $this->fdb->getForumByPostId ($post_id);

		if ($this->_checkUserPerm ('', $f['forum_type'], 'del', $f['forum_id'])) 
			$no_access = false;
		if ($no_access && ($user = $this->fdb->getPostUser((int)$post_id)) == $this->_getLoginUserName())
			if ($this->_checkUserPerm ('', 'own', 'del', $f['forum_id']) && !$this->_isEditTimeout((int)$post_id)) 
				$no_access = false;

		if ($no_access || 0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST'))
		{
			return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">
	window.parent.document.f.accessDenied();
</script>
</body>
</html>
EOF;
		}	
	
		// delete post here

		$this->fdb->deletePost ($post_id);

		$aTopic = $this->fdb->getTopic ($topic_id);
        $exists = $aTopic ? 1 : 0;

        if (is_callable($this->onPostDelete))
            call_user_func_array ($this->onPostDelete,  array($aTopic, $post_id, $user));

		return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">
	window.parent.document.f.deleteSuccess('{$f['forum_uri']}', '{$aTopic['topic_uri']}', {$exists});
</script>
</body>
</html>
EOF;

	}

	/**
	 * hide or unhide post
	 * @param $post_id		post id
	 * @param $topic_id		topic id
	 * @param $forum_id		forum id 
	 */
	function hidePost ($is_hide, $post_id)
	{
        $f = $this->fdb->getForumByPostId((int)$post_id);
        $user = $this->_getLoginUserName();
		if (0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') || !$this->_checkUserPerm ($user, '', 'hide_posts', $f ? $f['forum_id'] : 0))
			return '<ret>0</ret>';
        
		if (!$this->fdb->hidePost ((int)$is_hide, (int)$post_id, $user))
            return '<ret>0</ret>';

        if (is_callable($this->onPostHideUnhide))
            call_user_func_array ($this->onPostHideUnhide,  array($is_hide, $post_id, $user));

		return '<ret>1</ret>';
	}

	/**
	 * move topic
	 * @param $topic_id		topic id to move
	 */
    function moveTopicForm ($topic_id) {

        $cu = $this->getUrlsXml ();

        $t = $this->fdb->getTopic ($topic_id);

        $s = '<forums>';
		$ac = $this->fdb->getCategs();
		foreach ($ac as $c)
		{
            $s .= '<cat name="'. htmlspecialchars($c['cat_name']) .'">';
            
            $af = $this->fdb->getForumsByCatUri (filter_to_db($c['cat_uri']));

		    foreach ($af as $f)
    		{
			    encode_post_text ($f['forum_title']);
			    $s .= '<forum id="' . $f['forum_id'] . '" uri="' . $f['forum_uri'] . '" selected="' . ($t['forum_id'] == $f['forum_id'] ? 1 : 0) . '">' . $f['forum_title'] . '</forum>';
            }

            $s .= '</cat>';
		}
        $s .= '</forums>';

		return <<<EOS
<root>
$cu
<form>
    <topic>
	    <id>{$t['topic_id']}</id>
        <title>{$t['topic_title']}</title>
        <uri>{$t['topic_uri']}</uri>
        <forum_id>{$t['forum_id']}</forum_id>
    </topic>
    $s
</form>
</root>
EOS;
    }

    function moveTopicSubmit ($topic_id, $forum_id, $old_forum_id, $goto_new_location) {
        $user = $this->_getLoginUserName();
		if (0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') || !$this->_checkUserPerm ($user, '', 'move_topics', (int)$forum_id) || !$topic_id || !$forum_id || !$old_forum_id)
			return '<ret>0</ret>';
        
		if (!$this->fdb->moveTopic ((int)$topic_id, (int)$forum_id, (int)$old_forum_id))
            return '<ret>0</ret>';

        if (is_callable($this->onTopicMove))
            call_user_func_array ($this->onTopicMove,  array($topic_id, $forum_id, $user));
        
        $f = $this->fdb->getForum($goto_new_location ? $forum_id : $old_forum_id);
		return "<ret>{$f['forum_uri']}</ret>";
    }

	/**
	 * delete topic
	 * @param $topic_id		topic id
	 */
	function delTopic ($topic_id) {
        $f = $this->fdb->getForumByTopicId ((int)$topic_id);
        $user = $this->_getLoginUserName();
		if (0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') || !$this->_checkUserPerm ($user, '', 'del_topics', $f ? $f['forum_id'] : 0))
			return '<ret>0</ret>';
        
		if (!$this->fdb->delTopic ((int)$topic_id, $user))
            return '<ret>0</ret>';

        if (is_callable($this->onTopicDelete))
            call_user_func_array ($this->onTopicDelete,  array($topic_id, $user));

		return '<ret>1</ret>';
    }

	/**
	 * hide or unhide post
	 * @param $is_hide		hide or unhide
	 * @param $topic_id		topic id
	 */
	function hideTopic ($is_hide, $topic_id)
	{
        $f = $this->fdb->getForumByTopicId ((int)$topic_id);
        $user = $this->_getLoginUserName();
		if (0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') || !$this->_checkUserPerm ($user, '', $is_hide ? 'hide_topics' : 'unhide_topics', $f ? $f['forum_id'] : 0))
			return '<ret>0</ret>';
        
		if (!$this->fdb->hideTopic ((int)$is_hide, (int)$topic_id, $user))
            return '<ret>0</ret>';

        if (is_callable($this->onTopicHideUnhide))
            call_user_func_array ($this->onTopicHideUnhide,  array($is_hide, $topic_id, $user));

		return '<ret>1</ret>';
	}

	/**
	 * post new topic
	 * @param $p	_post array
	 */
	function postNewTopicXML ($p)
	{
		$f = $this->fdb->getForum ((int)$p['forum_id']);

		if (!$this->_checkUserPerm ('', $f['forum_type'], 'post', (int)$p['forum_id']) || bx_is_spam($p['topic_text'])) 
		{
			return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">

	if (window.parent.document.getElementById('tinyEditor'))
		window.parent.tinyMCE.execCommand('mceRemoveControl', false, 'tinyEditor');

	window.parent.document.f.accessDenied();

</script>
</body>
</html>
EOF;
		}		

		if ($p['topic_sticky'] == 'on' && !$this->_checkUserPerm ('', '', 'sticky', (int)$p['forum_id'])) 
		{
			return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">

	if (window.parent.document.getElementById('tinyEditor'))
		window.parent.tinyMCE.execCommand('mceRemoveControl', false, 'tinyEditor');

	window.parent.document.f.accessDenied();

</script>
</body>
</html>
EOF;
		}		


		// post mesage here

		$user = $this->_getLoginUserName ();
		
		prepare_to_db($p['topic_subject'], 1);
		prepare_to_db($p['topic_text'], 1);

        $topic_uri = $this->uriGenerate ($p['topic_subject'], TF_FORUM_TOPIC, 'topic_uri');
		$post_id = $this->fdb->newTopic ((int)$p['forum_id'], $p['topic_subject'], $p['topic_text'], ($p['topic_sticky'] == 'on'), $user, $topic_uri);

        $this->_handleSignature ($_POST, $user);

        $isUploadSuccess = $this->_handleUpload ($p, $post_id);

        if (is_callable($this->onNewTopic))
            call_user_func_array ($this->onNewTopic,  array((int)$p['forum_id'], $p['topic_subject'], $p['topic_text'], ($p['topic_sticky'] == 'on'), $user, $topic_uri));

		return <<<EOF
<html>
<body>
<script language="javascript" type="text/javascript">

    if (!$isUploadSuccess)
        window.parent.alert ('[L[Some or all files upload failed]]');

	if (window.parent.document.getElementById('tinyEditor'))
		window.parent.tinyMCE.execCommand('mceRemoveControl', false, 'tinyEditor');

	window.parent.document.f.selectTopic('{$topic_uri}');

</script>
</body>
</html>
EOF;

	}

	function isAdmin ()
	{
        $a = $this->_getUserInfo ($this->getLoginUser());
        return $a['admin'];
	}
	
	/**
	 * returns logged in user
	 */
	function getLoginUser ()
	{
		return $this->_getLoginUser();
	}

	/**
	 * updates current user last activity time
	 */ 
	function updateCurrentUserActivity ()
	{
		$u = $this->getLoginUser ();
		if (!$u) return;		

		$this->fdb->updateUserActivity ($u);
	}

	function logout ()
	{
		$u = $this->getLoginUser ();
		if (!$u) return '<ret>0</ret>';

		setcookie('orca_pwd', 'orca_pwd', time() - 86400);
		setcookie('orca_user', 'orca_user', time() - 86400);
		setcookie('track_topics', 'track_topics', time() - 86400);

		$this->fdb->updateUserLastActivity ($u);

		return '<ret>1</ret>';
	}

	/**
	 * post voting
	 *	@param $post_id	post id
	 *	@param $vote	vote (1|-1)
	 */ 
	function votePost ($post_id, $vote)
	{
		$u = $this->getLoginUser ();
		if (!$u || 0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST')) 
			return '<ret>0</ret>';
		
		if (!$this->fdb->insertVote ((int)$post_id, $u, $vote))
			return '<ret>0</ret>';

        if (is_callable($this->onVote))
            call_user_func_array ($this->onVote,  array((int)$post_id, $u, $vote));

		return '<ret>1</ret>';
	}

	/**
	 * report post
	 *	@param $post_id	post id
	 */ 
	function report ($post_id)
	{
		if (!$post_id || 0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST'))
			return '<ret>0</ret>';

		$u = $this->getLoginUser ();
		if (!$u) 
			return '<ret>0</ret>';
		
		if (!$this->fdb->report ((int)$post_id, $u))
			return '<ret>0</ret>';

        if (is_callable($this->onReport))
            call_user_func_array ($this->onReport,  array((int)$post_id, $u));

		return '<ret>1</ret>';
	}

	/**
	 * flag/unflag topic
	 *	@param $topic_id	topic id
	 */ 
	function flag ($topic_id)
	{
		if (!$topic_id || 0 != strcasecmp($_SERVER['REQUEST_METHOD'], 'POST')) 
			return '<ret>0</ret>';

		$u = $this->getLoginUser ();
		if (!$u) 
			return '<ret>0</ret>';		

		if ($this->fdb->isFlagged ((int)$topic_id, $u))
		{
			if (!$this->fdb->unflag ((int)$topic_id, $u))
                return '<ret>0</ret>';
            if (is_callable($this->onUnflag))
                call_user_func_array ($this->onUnflag,  array((int)$topic_id, $u));
			return '<ret>-1</ret>';
		}
		
		if (!$this->fdb->flag ((int)$topic_id, $u))
		    return '<ret>0</ret>';

        if (is_callable($this->onFlag))
            call_user_func_array ($this->onFlag,  array((int)$topic_id, $u));

		return '<ret>1</ret>';
	}

	/**
	 * forum rss feed, 10 latest topics in the forum
	 *	@param $forum_id	forum id	
	 */ 
	function getRssForum ($forum_uri)
	{
		global $gConf;

        $this->_rssPrepareConf ();

        $f = $this->fdb->getForumByUri (filter_to_db($forum_uri));
        $forum_id = $f['forum_id'];

		if (!$f) exit;

		$a = $this->fdb->getTopics ($forum_id, 0);

		reset ($a);
		$items = '';
		$lastBuildDate = '';
		while ( list (,$r) = each ($a) )
		{
			$lp = $this->fdb->getTopicPost($r['topic_id'], 'last');

			$td = $this->fdb->getTopicDesc ($r['topic_id']);
            $td = orca_mb_substr($td, 0, $gConf['topics_desc_len']);
            if (orca_mb_len($td) == $gConf['topics_desc_len']) 
                $td .= '...';

			if (!$lastBuildDate)
				$lastBuildDate = $lp['when'];

            $items .= $this->_rssItem ($r['topic_title'], sprintf($gConf['rewrite']['topic'], $r['topic_uri']), $td, $lp['when']);
		}		

        return $this->_rssFeed ($f['forum_title'], sprintf($gConf['rewrite']['forum'], $f['forum_uri'], 0), $f['forum_title'], $lastBuildDate, $items);
	}


	/**
	 * latest updates feed, 10 latest topics
	 *	@param $forum_id	forum id	
	 */ 
	function getRssUpdatedTopics ()
	{
		global $gConf;

        $this->_rssPrepareConf ();

		$a = $this->fdb->getRecentTopics (0);

		reset ($a);
		$items = '';
		$lastBuildDate = '';
		while ( list (,$r) = each ($a) )
		{
			$td = orca_mb_replace('/#/', $r['count_posts'], '[L[# posts]]') . ' &#8226; ' . orca_mb_replace('/#/', $r['last_post_user'], '[L[last reply by #]]') . ' &#8226; ' . $r['cat_name'] . ' &#187; ' . $r['forum_title'];

			if (!$lastBuildDate)
				$lastBuildDate = $r['last_when'];

            $items .= $this->_rssItem ($r['topic_title'], sprintf($gConf['rewrite']['topic'], $r['topic_uri']), $td, $r['last_when']);
		}		

        return $this->_rssFeed ('[L[Updated Topics]]', '', '[L[Updated Topics]]', $lastBuildDate, $items);
	}


	/**
	 * topic rss feed, 10 latest posts in the topic
	 *	@param $forum_id	forum id	
	 */ 
	function getRssTopic ($topic_uri)
	{
		global $gConf;
		
        $this->_rssPrepareConf ();

        $t = $this->fdb->getTopicByUri(filter_to_db($topic_uri));
        $topic_id = (int)$t['topic_id'];

		if (!$t) exit;

		$a = $this->fdb->getPosts ($topic_id, 0);

		reset ($a);
		$items = '';
		$lastBuildDate = '';
		while ( list (,$r) = each ($a) )
		{
			$lp = $this->fdb->getTopicPost((int)$r['topic_id'], 'last');
            $td = strip_tags(orca_mb_substr($r['post_text'], 0, 255));
            if (orca_mb_len($td) == 255) $td .= '[...]';
			$tt = orca_mb_substr($td, 0, 32);
			
				$lastBuildDate = $lp['when'];
        
            $items .= $this->_rssItem ($tt, sprintf($gConf['rewrite']['topic'], $t['topic_uri']), $td, $lp['when']);
		}		
			
        return $this->_rssFeed ($t['topic_title'], sprintf($gConf['rewrite']['topic'], $t['topic_uri']), $t['topic_title'], $lastBuildDate, $items);
	}
		
	/**
	 * user posts rss feed, 10 latest posts of specified user
     *	@param $user	username 
     *  @param $sort	sort : rnd | top | latest - default
	 */ 
	function getRssUser ($user, $sort)
	{
		global $gConf;

        $this->_rssPrepareConf ();
		
		$a = $this->fdb->getUserPostsList(filter_to_db($user), $sort, $gConf['topics_per_page']);

		reset ($a);
		$items = '';
		$lastBuildDate = '';
		while ( list (,$r) = each ($a) )
		{
			if (!$lastBuildDate)
				$lastBuildDate = $r['when'];

			$td = strip_tags($r['post_text']);
            if (strlen($td) == 256) $td .= '[...]';
			
            $items .= $this->_rssItem ($r['topic_title'], sprintf($gConf['rewrite']['topic'], $r['topic_uri']), "{$r['user']}: {$td}", $r['when']);
		}		
		
        if ($sort == 'rnd' || $sort == 'top') $lastBuildDate = '';

        $sTitle = sprintf("[L[%s's forum posts]]", $user);

        return $this->_rssFeed ($sTitle, '', $sTitle, $lastBuildDate, $items);
    }

	/**
	 * all posts rss feed, 10 latest posts
     *	@param $user	username 
     *  @param $sort	sort : rnd | top | latest - default
	 */ 
	function getRssAll ($sort)
	{
		global $gConf;

        $this->_rssPrepareConf ();
		
		$a = $this->fdb->getAllPostsList($sort, $gConf['topics_per_page']);

		reset ($a);
		$items = '';
		$lastBuildDate = '';
		while ( list (,$r) = each ($a) )
		{
			if (!$lastBuildDate)
				$lastBuildDate = $r['when'];

            $td = $r['post_text'];
            if (orca_mb_len($td) == 255) $td .= '[...]';
			$td = strip_tags($td);
            
		
            $items .= $this->_rssItem ($r['topic_title'], sprintf($gConf['rewrite']['topic'], $r['topic_uri']), "{$r['user']}: {$td}", $r['when']);
		}		
		
        if ($sort == 'rnd' || $sort == 'top') $lastBuildDate = '';
        
        return $this->_rssFeed ('[L[Forum Posts]]', '', '[L[Forum Posts]]', $lastBuildDate, $items);
    }
        
    function _rssPrepareConf () {
        global $gConf;
		$gConf['topics_per_page'] = 10;
		$gConf['date_format'] = '%a, %e %b %Y %k:%i:%s';
    }

    function _rssItem ($sTitle, $sLink, $sDesc, $sDate) {
        global $gConf;
		return "
			<item>
				<title><![CDATA[{$sTitle}]]></title>
				<link>" . $gConf['url']['base'] . $sLink . "</link>
				<description><![CDATA[{$sDesc}]]></description>
				<pubDate>{$sDate}</pubDate>
				<guid>" . $gConf['url']['base'] . $sLink . "</guid>
			</item>";
    }

    function _rssFeed ($sTitle, $sLink, $sDesc, $sLastDate, $sItems) {
        global $gConf;
		return <<<EOF
<rss version="2.0">
	<channel>
		<title>{$sTitle}</title>
		<link>{$gConf['url']['base']}{$sLink}</link>
		<description>{$sDesc}</description>
		<lastBuildDate>{$sLastDate}</lastBuildDate>
		$sItems
	</channel>
</rss>
EOF;
    }

	/** 
	 * profile xml
	 * @param	$u	username
	 * @param	$wp	return whole page XML
	 */ 
	function showProfile ($u, $wp)
	{
		$a = $this->_getUserInfo ($u);
		$as = $this->fdb->getUserStat (filter_to_db($u));

		$a['username'] = $u;
		$a['posts'] = (int)$as['posts'];
		$a['user_last_post'] = $as['user_last_post'];
		$a['last_online'] = $this->fdb->getUserLastOnlineTime (filter_to_db($u));

        encode_post_text ($as['role']);
		$a['role'] = $as['role'];

		$p = array2xml ($a);

		if ($wp)
		{
			if ($a)
				$this->setTitle ("<![CDATA[$u]]>");
			$li = $this->_getLoginInfo ();
			return $this->addHeaderFooter ($li, "<profile>$p</profile>");
		}
		else
		{
			$cu = $this->getUrlsXml ();
			return "<root>$cu<profile>$p</profile></root>";
		}		
	}

	// private functions

	function _getLoginInfo ($user = '')
	{
		if (!strlen($user)) $user = $this->_getLoginUserName ();
		$a = $this->_getUserInfo ($user);		
        $a['username'] = $user;
		return $a; 
	}

	function _getUserInfo ($user)
    {		
        if (is_callable($this->getUserInfo))
            return call_user_func_array ($this->getUserInfo,  array($user));
        return array ();
	}

	/**
	 * check user perms
	 * @param $user		username
	 * @param $f_type	forum type private/public/own
	 * @param $a_type	access type read/post/edit/del
	 */
	function _checkUserPerm ($user, $f_type, $a_type, $forum_id = 0)
    {
        if (is_callable($this->getUserPerm)) 
        {
            $a = call_user_func_array ($this->getUserPerm,  array($user, $f_type, $a_type, $forum_id));
            return $a["{$a_type}_{$f_type}"];
        }
        return false;
	}

	/**
	 * check timeout 
     * @param $post_id	post id
	 */
	function _isEditTimeout ($post_id)
    {
        if ((time() - $this->fdb->getPostWhen($post_id) - 10) > $GLOBALS['gConf']['edit_timeout'])
            return true;
        return false;
    }

	/**
	 * returns loggen in user
	 */
	function _getLoginUserName ()
	{
		return $this->_getLoginUser();
	}

	/**
	 * returns logged in user
	 */
	function _getLoginUser ()
    {
        if (is_callable($this->getLoginUser))
            return call_user_func ($this->getLoginUser);
        return '';
	}

    function _format_when ($iSec)
    {
        $s = '<b>';
        if ($iSec < 3600)
        {
            $i = round($iSec/60);
            if (0 == $i || 1 == $i) $s .= '1</b> [L[Minute Ago]]';
            else $s .= $i . '</b> [L[Minutes Ago]]';
        }
        else if ($iSec < 86400)
        {
            $i = round($iSec/60/60);
            if (0 == $i || 1 == $i) $s .= '1</b> [L[Hour Ago]]';
            else $s .= $i . '</b> [L[Hours Ago]]';
        }                
        else 
        {
            $i = round($iSec/60/60/24);
            if (0 == $i || 1 == $i) $s .= '1</b> [L[Day Ago]]';
            else $s .= $i . '</b> [L[Days Ago]]';
        }                            
        return $s;     
    }  

    function _no_access ($wp = 0)
    {
        $xml = '<forum_access>no</forum_access>';
        if (!$wp) return $xml;
        $li = $this->_getLoginInfo ();
        return $this->addHeaderFooter ($li, $xml);
    }

    function _buld_topic_desc (&$s)
    {
        $s = str_replace (array('&#160;','&amp;','&gt;','&lt;','&quot;'), array(' ','&','>','<',"'"), $s);
        $s = strip_tags ($s);
        validate_unicode ($s);
        if ($s == '') $s = ' ';
		$s = '<![CDATA[' . $s . ']]>';
	}

    function uriGenerate ($s, $sTable, $sField, $iMaxLen = 255)
    {
        if ($GLOBALS['oTemplConfig']->bAllowUnicodeInPreg)
            $s = orca_mb_replace ('/[^\pL^\pN]+/u', '-', $s); // unicode characters
        else
            $s = orca_mb_replace ('/([^\d^\w]+)/u', '-', $s); // latin characters
            
        $s = orca_mb_replace ('/([-^]+)/', '-', $s);
        if (!$s) $s = '-';

        if ($this->uriCheckUniq($s, $sTable, $sField)) return $s;

        // try to add date

        if (orca_mb_len($s) > 240)
            $s = orca_mb_substr ($s, 0, 240);

        $s .= '-' . date('Y-m-d');
        
        if ($this->uriCheckUniq($s, $sTable, $sField)) return $s;


        // try to add number

        for ($i = 0 ; $i < 999 ; ++$i)
        {        
            if ($this->uriCheckUniq($s . '-' . $i, $sTable, $sField)) 
            {
                return ($s . '-' . $i);                
            }
        }

        return rand(0, 999999999);
    }

    function uriCheckUniq ($s, $sTable, $sField)
    {
        return !$this->fdb->getOne("SELECT 1 FROM $sTable WHERE $sField = '$s' LIMIT 1");
    }

	function setTitle ($s) 
	{
		$s = str_replace(array('<![CDATA[', ']]>'), '', $s);
		$GLOBALS['gConf']['title'] = '<![CDATA[' . $s . '[L[add this to title]]' . ']]>';
	}

    function _handleSignature (&$p, $user) {

        if (isset($p['signature']))
            $p['signature'] = trim ($p['signature']);
        if (!isset($p['signature']) || !$p['signature'])
            return true;

        prepare_to_db ($p['signature'], 0);

        return $this->fdb->updateSignature ($p['signature'], $user);
    }

    function _handleUpload (&$p, $post_id) {

        global $gConf;

        // handle existing uploads

        $attachments = $this->fdb->getAttachments ($post_id);
        foreach ($attachments as $file) {
            if (false !== array_search ($file['att_hash'], $p['existing_file'])) 
                continue;                
            $this->fdb->removeAttachment($file['att_hash']);
        }

        // handle new uploads

        $failed_uploads = 0;

        foreach ($_FILES['attachments']['error'] as $i => $error) {

            if (UPLOAD_ERR_NO_FILE == $error)
                continue;

            if ($error != UPLOAD_ERR_OK || !$_FILES['attachments']['size'][$i]) {
                ++$failed_uploads;
                continue;
            }

            $tmp_name = $_FILES["attachments"]["tmp_name"][$i];
            $name = $_FILES["attachments"]["name"][$i];
            $type = $_FILES["attachments"]["type"][$i];
            $size = (int)$_FILES["attachments"]["size"][$i];
            $hash = $this->fdb->genFileHash();
            $path = $gConf['dir']['attachments'] . orca_build_path ($hash);
            
            orca_mkdir_r ($path);

            if (!move_uploaded_file($tmp_name, $path . $hash)) {
                ++$failed_uploads;
                continue;
            }
           
            prepare_to_db ($name, 0);
            prepare_to_db ($type, 0);

            if (!$this->fdb->insertAttachment ($post_id, $hash, $name, $type, $size)) {
                ++$failed_uploads;
                continue;
            }

        }

        return $failed_uploads > 0 ? 0 : 1;
    }

    function download($hash) {

        global $gConf;
        
        prepare_to_db ($hash, 0);
        $a = $this->fdb->getAttachment($hash);
        if (!$a) {
            header("HTTP/1.1 404 Not Found");
            echo '404 Not Found';
            exit;
        } 

		if (!$this->_checkUserPerm ('', '', 'download', (int)$a['forum_id'])) {
            transCheck ($this->_no_access(1), $gConf['dir']['xsl'] . 'search_form_main.xsl', $_GET['debug'] ? 0 : 1);
            exit;
		}

        $this->fdb->updateAttachmentDownloads ($hash);        

        header ("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header ("Content-type: {$a['att_type']}");
        header ("Content-Length: " . $a['att_size']);
        header ("Content-Disposition: attachment; filename=\"{$a['att_name']}\"");

        readfile ($gConf['dir']['attachments'] . orca_build_path ($hash) . $hash);

        exit;    
    }

    function _getAttachmentsXML ($post_id) {

        $attachments = $this->fdb->getAttachments ($post_id);
        $files = '';
        foreach ($attachments as $file) {
            encode_post_text ($file['att_name']);
            $files .= '<file hash="' . $file['att_hash'] . '" size="' . orca_format_bytes($file['att_size']) . '" downloads="' . $file['att_downloads'] . '">' . $file['att_name'] . '</file>';
        }

        return $files;
    }

}

function orca_mb_replace ($sPattern, $sReplace, $s)
{
    return preg_replace ($sPattern, $sReplace, $s);
}

function orca_mb_len ($s)
{
    if (function_exists('mb_strlen'))
        return mb_strlen ($s);
    else
        return strlen ($s);
}    

function orca_mb_substr ($s, $iStart, $iLen)
{
    if (function_exists('mb_substr'))
        return mb_substr ($s, $iStart, $iLen);
    else
        return substr ($s, $iStart, $iLen);
}    

?>
