<?php
/**
 * <pre>
 * Invision Power Services
 * IP.Board v3.2.3
 * Moderator actions
 * Last Updated: $Date: 2011-10-14 12:56:17 -0400 (Fri, 14 Oct 2011) $
 * </pre>
 *
 * @author 		$Author: ips_terabyte $
 * @copyright	(c) 2001 - 2009 Invision Power Services, Inc.
 * @license		http://www.invisionpower.com/community/board/license.html
 * @package		IP.Board
 * @subpackage	Forums
 * @link		http://www.invisionpower.com
 * @version		$Revision: 9622 $
 *
 */

if ( ! defined( 'IN_IPB' ) )
{
	print "<h1>Incorrect access</h1>You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files.";
	exit();
}

class moderatorLibrary
{
	/**
	 * Moderator information
	 *
	 * @var		array		Array of moderator details
	 */
	public $moderator		= array();

	/**
	 * Forum information
	 *
	 * @var		array		Array of forum details
	 */
	public $forum			= array();

	/**
	 * Topic information
	 *
	 * @var		array		Array of topic details
	 */
	public $topic			= array();
	
	/**
	 * Error code encountered
	 *
	 * @var		string		Error code
	 */
	public $error 			= "";

	/**
	 * Stored statement
	 *
	 * @var		string		Stored multi-mod statement
	 */
	public $stm				= "";

	/**#@+
	 * Registry Object Shortcuts
	 * 
	 * @var		object
	 */
	protected $registry;
	protected $DB;
	protected $settings;
	protected $request;
	protected $lang;
	protected $member;
	protected $memberData;
	protected $cache;
	protected $caches;
	/**#@-*/
	
	/**
	 * Class entry point
	 *
	 * @param	object		Registry reference
	 * @return	@e void
	 */
	public function __construct( ipsRegistry $registry )
	{
		/* Make objects */
		$this->registry = $registry;
		$this->DB	    = $this->registry->DB();
		$this->settings =& $this->registry->fetchSettings();
		$this->request  =& $this->registry->fetchRequest();
		$this->lang	    = $this->registry->getClass('class_localization');
		$this->member   = $this->registry->member();
		$this->memberData =& $this->registry->member()->fetchMemberData();
		$this->cache	= $this->registry->cache();
		$this->caches   =& $this->registry->cache()->fetchCaches();
		
		/* Check for class_forums */
		if( ipsRegistry::isClassLoaded('class_forums') !== TRUE )
		{
			$classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'forums' ) . "/sources/classes/forums/class_forums.php", 'class_forums', 'forums' );
			$this->registry->setClass( 'class_forums', new $classToLoad( $registry ) );
			$this->registry->class_forums->forumsInit();
		}
		
		/* Load tagging stuff */
		if ( ! $this->registry->isClassLoaded('tags') )
		{
			require_once( IPS_ROOT_PATH . 'sources/classes/tags/bootstrap.php' );/*noLibHook*/
			$this->registry->setClass( 'tags', classes_tags_bootstrap::run( 'forums', 'topics' ) );
		}
	}
	
	/**
	 * Initialization
	 *
	 * @param	array 		Forum array
	 * @param 	array 		[Optional] Topic array
	 * @param	array 		[Optional] Moderator array
	 * @return	boolean		True
	 */
	public function init($forum="", $topic="", $moderator="")
	{
		$this->forum = $forum;
		
		if ( is_array($topic) )
		{
			$this->topic = $topic;
		}
		
		if ( is_array($moderator) )
		{
			$this->moderator = $moderator;
		}
		
		return true;
	}
	
	/**
	 * Toggle approve status of content by a member
	 * WARNING: This is a utility function. No permission checks are performed. 
	 *
	 * @param	int			Member ID (topic starter / post author)
	 * @param	boolean		TRUE = approve, FALSE = unapprove
	 * @param	string		Option [ topics / replies / all ]
	 * @param	int			[ Optional: last X hours worth of data ]
	 * @return	boolean
	 */
	public function toggleApproveMemberContent( $memberID, $approve=FALSE, $option, $date=0 )
	{
		$memberID  = intval( $memberID );
		$date	   = intval( $date );
		$timeCut   = ( $date ) ? ( time() - ( $date * 3600 ) ) : 0;
		$topicFind = '';
		$postFind  = '';
		$forumIDs  = array();
		$topicIDs  = array();
		
		if ( ! $memberID )
		{
			return FALSE;
		}
		
		switch ( $option )
		{
			default:
			case 'all':
			case 'both':
			case 'topics':
				$postFind  = 'author_id=' . $memberID . ' AND new_topic=0';
				$postFind .= ( $timeCut ) ? ' AND post_date > ' . $timeCut : '';
				$topicFind  = 'starter_id=' . $memberID;
				$topicFind .= ( $timeCut ) ? ' AND start_date > ' . $timeCut : '';
			break;
			case 'replies':
			case 'posts':
				$postFind  = 'author_id=' . $memberID . ' AND new_topic=0';
				$postFind .= ( $timeCut ) ? ' AND post_date > ' . $timeCut : '';
			break;
		}
		
		//-----------------------------------------
		// Find forums..
		//-----------------------------------------
		
		if ( $topicFind )
		{
			$this->DB->build( array( 'select' => $this->DB->buildDistinct( 'forum_id' ) . ',tid',
							  		 'from'   => 'topics',
									 'where'  => $topicFind ) );
			$this->DB->execute();
			
			while( $row = $this->DB->fetch() )
			{
				$forumIDs[ $row['forum_id'] ]	= $row['forum_id'];
				$topicIDs[ $row['tid'] ]		= $row['tid'];
			}
		}
		
		if ( $postFind )
		{
			$this->DB->build( array( 'select'   => $this->DB->buildDistinct( 't.forum_id' ) . ',t.tid',
							  		 'from'     => array( 'posts' => 'p' ),
									 'where'    => $postFind,
									 'add_join' => array( array( 'select' => '',
																 'from'   => array( 'topics' => 't' ),
																 'where'  => 'p.topic_id=t.tid' ) ) ) );
			$this->DB->execute();
			
			while( $row = $this->DB->fetch() )
			{
				$forumIDs[ $row['forum_id'] ]	= $row['forum_id'];
				$topicIDs[ $row['tid'] ]		= $row['tid'];
			}
		}
		
		//-----------------------------------------
		// Run...
		//-----------------------------------------
		
		if ( $topicFind AND count($topicIDs) )
		{
			$this->DB->update( 'topics', array( 'approved' => ( $approve === TRUE ) ? 1 : 0 ), $topicFind );
			
			/* Tagging */
			$this->registry->tags->updateVisibilityByMetaId( $topicFind, ( $approve === TRUE ? 1 : 0 ) );
			
			/* Likes */
			require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
			$_like	= classes_like::bootstrap( 'forums', 'topics' );
			$_like->toggleVisibility( $topicIDs, $approve );
		}
		
		if ( $postFind )
		{
			$this->DB->update( 'posts', array( 'queued' => ( $approve === TRUE ) ? 0 : 1 ), $postFind );
		}

		if ( count( $topicIDs ) )
		{
			foreach( $topicIDs as $id )
			{
				$this->rebuildTopic( $id );
			}
		}

		if ( count( $forumIDs ) )
		{
			foreach( $forumIDs as $id )
			{
				$this->forumRecount( $id );
			}
		}
		
		$this->cache->rebuildCache( 'stats', 'global' );
		
		return TRUE;
	}
	
	/**
	 * Delete content by a member
	 * WARNING: This is a utility function. No permission checks are performed.
	 *
	 * @param	int			Member ID (topic starter / post author)
	 * @param	string		Option [ topics / replies / all ]
	 * @param	int			[ Optional: last X hours worth of data ]
	 * @return	boolean
	 */
	public function deleteMemberContent( $memberID, $option, $date=0 )
	{
		$memberID  = intval( $memberID );
		$date	   = intval( $date );
		$timeCut   = ( $date ) ? ( time() - ( $date * 3600 ) ) : 0;
		$topicFind = '';
		$postFind  = '';
		$topicIDs  = array();
		$postIDs   = array();
		$forumIDs  = array();
		
		if ( ! $memberID )
		{
			return FALSE;
		}
		
		switch ( $option )
		{
			default:
			case 'all':
			case 'both':
			case 'topics':
				$postFind  = 'author_id=' . $memberID . ' AND new_topic=0';
				$postFind .= ( $timeCut ) ? ' AND post_date > ' . $timeCut : '';
				$topicFind  = 'starter_id=' . $memberID;
				$topicFind .= ( $timeCut ) ? ' AND start_date > ' . $timeCut : '';
			break;
			case 'replies':
			case 'posts':
				$postFind  = 'author_id=' . $memberID . ' AND new_topic=0';
				$postFind .= ( $timeCut ) ? ' AND post_date > ' . $timeCut : '';
			break;
		}
		
		//-----------------------------------------
		// Run...
		//-----------------------------------------
		
		if ( $topicFind )
		{
			/* Update the posts to delete */
			$this->DB->update( 'topics', array( 'approved' => $this->registry->class_forums->fetchTopicHiddenFlag('pdelete') ), $topicFind );
		
			$this->DB->build( array( 'select' => $this->DB->buildDistinct( 'forum_id' ),
							  		 'from'   => 'topics',
									 'where'  => $topicFind ) );
			$this->DB->execute();
			
			while( $row = $this->DB->fetch() )
			{
				$forumIDs[ $row['forum_id'] ] = $row['forum_id'];
			}
			
			/* Fetch unique topic IDs */
			$this->DB->build( array( 'select'   => 'tid',
							  		 'from'     => 'topics',
									 'where'    => $topicFind,
									 'order'    => 'topic_id DESC',
									 'limit'    => array( 0, 2000 ) ) );
			$this->DB->execute();
			
			while( $row = $this->DB->fetch() )
			{
				$tagTopicIDs[] = $row['topic_id'];
			}
			
			/* Tagging */
			if ( $this->registry->tags->isEnabled() )
			{
				$this->registry->tags->updateVisibilityByMetaId( $tagTopicIDs, 0 );
			}

			/* Likes */
			require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
			$_like	= classes_like::bootstrap( 'forums', 'topics' );
			$_like->toggleVisibility( $tagTopicIDs, false );
		}
		
		if ( $postFind )
		{
			/* Update the posts to delete */
			$this->DB->update( 'posts', array( 'queued' => $this->registry->class_forums->fetchPostHiddenFlag('pdelete'), 'pdelete_time' => IPS_UNIX_TIME_NOW ), $postFind );
		
			/* Fetch unique forum IDs */
			$this->DB->build( array( 'select'   => $this->DB->buildDistinct( 't.forum_id' ),
							  		 'from'     => array( 'topics' => 't' ),
									 'where'    => 'p.author_id=' . $memberID . ' AND p.new_topic=0 AND p.topic_id=t.tid',
									 'add_join' => array( array( 'select' => '',
																 'from'   => array( 'posts' => 'p' ),
																 'type'   => 'inner' ) ) ) );
			$this->DB->execute();
			
			while( $row = $this->DB->fetch() )
			{
				$forumIDs[ $row['forum_id'] ] = $row['forum_id'];
			}
			
			/* Fetch unique topic IDs */
			$this->DB->build( array( 'select'   => $this->DB->buildDistinct( 'topic_id' ),
							  		 'from'     => 'posts',
									 'where'    => $postFind,
									 'order'    => 'topic_id DESC',
									 'limit'    => array( 0, 2000 ) ) );
			$this->DB->execute();
			
			while( $row = $this->DB->fetch() )
			{
				$topicIDs[ $row['topic_id'] ] = $row['topic_id'];
			}
		}
		
		//-----------------------------------------
		// Delete topics...
		//-----------------------------------------
		
		if ( is_array( $topicIDs ) AND count( $topicIDs ) )
		{
			foreach( $topicIDs as $id )
			{
				$this->rebuildTopic( $id, 0 );
			}
		}
		
		if ( count( $forumIDs ) )
		{
			foreach( $forumIDs as $id )
			{
				$this->registry->class_forums->allForums[ $id ]['_update_deletion'] = 1;
				$this->forumRecount( $id );
			}
		}
		
		$this->cache->rebuildCache( 'stats', 'global' );
		
		return TRUE;
	}
	
	/**
	 * Delete / undelete posts
	 *
	 * @param	array 		Array of Post IDs
	 * @param	boolean		Approve (TRUE) / Unapprove (FALSE)
	 * @param	int			Fix so posts can only come from a specific topic ID
	 * @return	boolean
	 */
	public function postToggleSoftDelete( $postIDs, $delete=FALSE, $reason='', $topicIDFix=0 )
	{
		//-----------------------------------------
		// INIT
		//-----------------------------------------
		
		$_restoreTopic	= 1;
		$_deletedPost	= 0;
		$_pdeleteTs		= 0;
		
		if ( $delete === TRUE )
		{
			$_restoreTopic	= -1;
			$_deletedPost	= 2;
			$_pdeleteTs		= IPS_UNIX_TIME_NOW;
		}
		
		$_topics	= array();
		$_forumIDs	= array();
		$_pids		= IPSLib::cleanIntArray( $postIDs );
		$_tids		= array();
		
		//-----------------------------------------
		// Fetch distinct topic IDs
		//-----------------------------------------
		
		$this->DB->build( array( 'select' => $this->DB->buildDistinct( 'p.topic_id' ),
								 'from'   => array( 'posts' => 'p' ),
								 'where'  => 'p.pid IN (' . implode( ',', $postIDs ) . ')',
								 'add_join' => array( array( 'select' => 't.*',
															 'from'   => array( 'topics' => 't' ),
															 'where'  => 'p.topic_id=t.tid',
															 'type'   => 'inner' ) ) ) );
								
		$this->DB->execute();
		
		while( $row = $this->DB->fetch() )
		{
			$_topics[ $row['topic_id'] ] = $row;
		}

		//-----------------------------------------
		// Did we get the first post too?
		//-----------------------------------------
		
		foreach( $_topics as $tid => $topicData )
		{
			/* Fix to a topic? */
			if ( $topicIDFix AND ( $topicIDFix != $tid ) )
			{
				continue;
			}
			
			$_forumIDs[] = $topicData['forum_id'];
			
			if ( in_array( $topicData['topic_firstpost'], $_pids ) )
			{
				$this->DB->update( 'topics', array( 'approved' => $_restoreTopic ), 'tid=' . $tid );
				
				$tmp    = $_pids;
				$_pids	= array();
			
				foreach( $tmp as $t )
				{
					if ( $t != $topicData['topic_firstpost'] )
					{
						$_pids[] = $t;
					}
					else
					{
						$_tids[ $topicData['tid'] ]	= $topicData['tid'];
					}
				}
			}
		}
	
		if ( count( $_pids ) )
		{
			$this->DB->update( 'posts', array( 'queued' => $_deletedPost, 'pdelete_time' => $_pdeleteTs ), 'pid IN (' . implode( ",", $_pids ) . ')' );
			
			if ( $_deletedPost )
			{
				foreach( $_pids as $_p )
				{
					/* Add entry to delete log innit */
					IPSDeleteLog::addEntry( $_p, 'post', $reason, $this->memberData );
				}
			}
			else
			{
				/* Un-deleting, so delete */
				IPSDeleteLog::removeEntries( $_pids, 'post' );
			}
		}
		
		if ( count( $_tids ) )
		{
			if ( $_deletedPost )
			{
				foreach( $_tids as $_t )
				{
					IPSDeleteLog::addEntry( $_t, 'topic', $reason, $this->memberData );
				}
			}
			else
			{
				/* Un-deleting, so delete */
				IPSDeleteLog::removeEntries( $_tids, 'topic' );
			}
		}
		
		if ( $delete )
		{
			foreach( $_topics as $tid => $topicData )
			{
				$this->addModerateLog( $topicData['forum_id'], $tid, 0, $topicData['title'], sprintf( $this->lang->words['acp_softdeleted_posts'], count( $_pids ), $topicData['title'] ) );
			}
		}
		else
		{
			foreach( $_topics as $tid => $topicData )
			{
				$this->addModerateLog( $topicData['forum_id'], $tid, 0, $topicData['title'], sprintf( $this->lang->words['acp_unsoftdeleted_posts'], count( $_pids ), $topicData['title'] ) );
			}
		}
		
		foreach( $_topics as $tid => $topicData )
		{
			$this->rebuildTopic( $tid );
		}
		
		if ( count( $_forumIDs ) )
		{
			foreach( $_forumIDs as $_fid )
			{
				$this->forumRecount( $_fid );
			}
		}
		
		$this->cache->rebuildCache( 'stats', 'global' );
		
		return TRUE;
	}
	
	/**
	 * Approve / unapprove posts
	 *
	 * @param	array 		Array of Post IDs
	 * @param	boolean		Approve (TRUE) / Unapprove (FALSE)
	 * @param	int			Fix so posts can only come from a specific topic ID
	 * @return	boolean
	 */
	public function postToggleApprove( $postIDs, $approve=FALSE, $topicIDFix=0 )
	{
		//-----------------------------------------
		// INIT
		//-----------------------------------------
		
		$_approveTopic	= 1;
		$_queuedPost	= 0;
		
		if ( $approve === FALSE )
		{
			$_approveTopic = 0;
			$_queuedPost   = 1;
		}
		
		$_topics	= array();
		$_forumIDs	= array();
		$_pids		= IPSLib::cleanIntArray( $postIDs );
		$_tids		= array();
						
		//-----------------------------------------
		// Fetch distinct topic IDs
		//-----------------------------------------
		
		$this->DB->build( array( 'select' => $this->DB->buildDistinct( 'p.topic_id' ),
								 'from'   => array( 'posts' => 'p' ),
								 'where'  => 'p.pid IN (' . implode( ',', $postIDs ) . ')',
								 'add_join' => array( array( 'select' => 't.*',
															 'from'   => array( 'topics' => 't' ),
															 'where'  => 'p.topic_id=t.tid',
															 'type'   => 'inner' ) ) ) );
								
		$this->DB->execute();
		
		while( $row = $this->DB->fetch() )
		{
			$_topics[ $row['topic_id'] ] = $row;
		}

		//-----------------------------------------
		// Did we get the first post too?
		//-----------------------------------------
		
		foreach( $_topics as $tid => $topicData )
		{
			/* Fix to a topic? */
			if ( $topicIDFix AND ( $topicIDFix != $tid ) )
			{
				continue;
			}
			
			$_forumIDs[] = $topicData['forum_id'];
			
			if ( in_array( $topicData['topic_firstpost'], $_pids ) )
			{
				$this->DB->update( 'topics', array( 'approved' => $_approveTopic ), 'tid=' . $tid );
			
				/* Unapprove the topic, but not the first post? */
				//if ( $_queuedPost )
				//{
					$tmp    = $_pids;
					$_pids	= array();
				
					foreach( $tmp as $t )
					{
						if ( $t != $topicData['topic_firstpost'] )
						{
							$_pids[] = $t;
						}
						else
						{
							/* @link	http://community.invisionpower.com/tracker/issue-33622-approveunapprove-and-old-posts */
							$_pids[]					= $t;
							$_tids[ $topicData['tid'] ]	= $topicData['tid'];
						}
					}
				//}
			}
		}
			
		if ( count( $_pids ) )
		{
			$this->DB->update( 'posts', array( 'queued' => $_queuedPost ), 'pid IN (' . implode( ",", $_pids ) . ')' );
		}
		
		if ( $approve )
		{
			foreach( $_topics as $tid => $topicData )
			{
				$this->addModerateLog( $topicData['forum_id'], $tid, 0, $topicData['title'], sprintf( $this->lang->words['acp_approved_posts'], count( $_pids ), $topicData['title'] ) );
			}
			
			if( count($_pids) )
			{
				$this->clearModQueueTable( 'post', $_pids, true );
			}
			
			if( count($_tids) )
			{
				$this->clearModQueueTable( 'topic', $_tids, true );
			}
		}
		else
		{
			foreach( $_topics as $tid => $topicData )
			{
				$this->addModerateLog( $topicData['forum_id'], $tid, 0, $topicData['title'], sprintf( $this->lang->words['acp_unapproved_posts'], count( $_pids ), $topicData['title'] ) );
			}
		}
		
		foreach( $_topics as $tid => $topicData )
		{
			$this->rebuildTopic( $tid );
		}
		
		if ( count( $_forumIDs ) )
		{
			foreach( $_forumIDs as $_fid )
			{
				$this->forumRecount( $_fid );
			}
		}
		
		$this->cache->rebuildCache( 'stats', 'global' );
		
		return TRUE;
	}
	
	/**
	 * Clear out the mod-queue table appropriately
	 *
	 * @param	string		[topic|post] Type of item moved
	 * @param	mixed		ID of topic or post, or array of ids
	 * @param	boolean		Was content approved?
	 * @return	@e void
	 */
	public function clearModQueueTable( $type, $typeId, $approved=false )
	{
		//-----------------------------------------
		// Get post class..
		//-----------------------------------------

		require_once( IPSLib::getAppDir( 'forums' ) . '/sources/classes/post/classPost.php' );/*noLibHook*/
		$classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'forums' ) . '/sources/classes/post/classPostForms.php', 'classPostForms', 'forums' );
		$_postClass  = new $classToLoad( $this->registry );
		
		//-----------------------------------------
		// Are we operating on one id, or an array
		//-----------------------------------------
		
		if( is_array($typeId) )
		{
			$where	= "type_id IN(" . implode( ',', IPSLib::cleanIntArray($typeId) ) . ")";
		}
		else
		{
			$where	= "type_id=" . intval($typeId);
		}

		//-----------------------------------------
		// Was content deleted
		//-----------------------------------------
				
		if( ! $approved )
		{
			$this->DB->delete( 'mod_queued_items', "type='{$type}' AND {$where}" );
		}

		//-----------------------------------------
		// No, then we are approving content
		//-----------------------------------------
		
		else
		{
			//-----------------------------------------
			// Working with posts?
			//-----------------------------------------
			
			if( $type == 'post' )
			{
				IPSDebug::fireBug( 'info', array( 'type is post' ) );
				
				$this->DB->build( array(
										'select'	=> 'm.id',
										'from'		=> array( 'mod_queued_items' => 'm' ),
										'where'		=> "m.type='{$type}' AND m.{$where}",
										'add_join'	=> array(
															array(
																'select'	=> 'p.pid, p.post, p.author_id, p.post_date, p.topic_id',
																'from'		=> array( 'posts' => 'p' ),
																'where'		=> 'p.pid=m.type_id',
																'type'		=> 'left',
																),
															array(
																'select'	=> 't.title, t.forum_id',
																'from'		=> array( 'topics' => 't' ),
																'where'		=> 't.tid=p.topic_id',
																'type'		=> 'left',
																),
															)
								)		);
				$outer = $this->DB->execute();

				while( $r = $this->DB->fetch($outer) )
				{
					$member	= IPSMember::load( $r['author_id'], 'extendedProfile,groups' );
					$_postClass->setPublished( true );
					$_postClass->setAuthor( $member );
					$_postClass->setForumData( $this->registry->class_forums->allForums[ $r['forum_id'] ] );
					
					$_postClass->incrementUsersPostCount();
					$_postClass->sendOutTrackedTopicEmails( $r['topic_id'], $r['post'], $member['members_display_name'], time() - $this->settings['session_expiration'], $member['member_id'] );
										
					$this->DB->delete( 'mod_queued_items', 'id=' . $r['id'] );
				}
			}
			else
			{
				IPSDebug::fireBug( 'info', array( 'type is topic' ) );
				
				$this->DB->build( array(
										'select'	=> 'm.id',
										'from'		=> array( 'mod_queued_items' => 'm' ),
										'where'		=> "m.type='{$type}' AND m.{$where}",
										'add_join'	=> array(
															array(
																'select'	=> 't.tid, t.title, t.starter_id, t.forum_id',
																'from'		=> array( 'topics' => 't' ),
																'where'		=> 't.tid=m.type_id',
																'type'		=> 'left',
																),
															array(
																'select'	=> 'p.pid, p.post, p.post_date',
																'from'		=> array( 'posts' => 'p' ),
																'where'		=> 'p.pid=t.topic_firstpost',
																'type'		=> 'left',
																),
															)
								)		);
				$outer = $this->DB->execute();
				
				while( $r = $this->DB->fetch($outer) )
				{
					$member	= IPSMember::load( $r['starter_id'], 'extendedProfile,groups' );
					$_postClass->setPublished( true );
					$_postClass->setAuthor( $member );
					$_postClass->setForumData( $this->registry->class_forums->allForums[ $r['forum_id'] ] );
					
					$_postClass->incrementUsersPostCount();
					$_postClass->sendOutTrackedForumEmails( $r['forum_id'], $r['tid'], $r['title'], $this->registry->class_forums->allForums[ $r['forum_id'] ]['name'], $r['post'], $member['member_id'], $member['members_display_name'] );
										
					$this->DB->delete( 'mod_queued_items', 'id=' . $r['id'] );
				}
				
			}
		}
				
		$this->addModerateLog( $this->request['f'], $this->request['t'], $this->request['p'], $this->topic['title'], sprintf( $this->lang->words['modqueue_table_clear'], $type, is_array($typeId) ? implode( ', ', $typeId ) : $typeId ) );
	}

	/**
	 * Delete a post
	 *
	 * @param	mixed 		Post id | Array of post ids
	 * @return	array		Topic IDs
	 */
	public function postDelete($id)
	{
		$posts			= array();
		$attach_tid		= array();
		$attach_ids		= array();
		$topics			= array();
		$fids           = array();
		$this->error	= "";

		if ( is_array( $id ) )
		{
			$id = IPSLib::cleanIntArray( $id );
			
			if ( count($id) > 0 )
			{
				$pid = " IN(" . implode( ",", $id ) . ")";
			}
			else
			{
				return false;
			}
		}
		else
		{
			if ( intval($id) )
			{
				$pid   = "={$id}";
			}
			else
			{
				return false;
			}
		}
		
		/* Set ids */
		$_ids = ( is_array( $id ) ) ? $id : array( $id );
		
		/* Fetch unique topic IDs */
		$this->DB->build( array( 'select'   => $this->DB->buildDistinct( 'topic_id' ),
						  		 'from'     => 'posts',
								 'where'    => 'pid' . $pid ) );
		$this->DB->execute();
		
		while( $row = $this->DB->fetch() )
		{
			$topics[ $row['topic_id'] ] = $row['topic_id'];
		}
			
		/* Update the posts to delete */
		$this->DB->update( 'posts', array( 'queued' => $this->registry->class_forums->fetchPostHiddenFlag('pdelete'), 'pdelete_time' => IPS_UNIX_TIME_NOW ), 'pid' . $pid );
		
		//-----------------------------------------
		// Update the stats
		//-----------------------------------------
		
		$this->cache->rebuildCache( 'stats', 'global' );

		//-----------------------------------------
		// Update all relevant topics
		//-----------------------------------------
		
		foreach( array_keys($topics) as $tid )
		{
			$this->rebuildTopic( $tid );
		}
		
		if ( count( $topics ) )
		{
			$this->DB->build( array( 'select' => 'forum_id',
									 'from'   => 'topics',
									 'where'  => 'tid IN (' . implode( ',', array_keys( $topics ) ) . ')' ) );
									 
			$i = $this->DB->execute();
			
			while( $row = $this->DB->fetch( $i ) )
			{
				$fids[ $row['forum_id'] ] = $row['forum_id'];
			}
		}
		
		if ( count( $fids ) )
		{
			foreach( $fids as $f )
			{
				$this->forumRecount( $f );
			}
		}
		
		/* Run moderation sync */
		$this->runModSync( 'postDelete', array_keys( $posts ) );
		
		//-----------------------------------------
		// Log and return
		//-----------------------------------------
		
		$pid = str_replace( array( 'IN', '(', ')', '=' ), '', $pid );
		
		$this->addModerateLog( "", "", "", $this->lang->words['mod_from_id'] . ' ' . implode( ',', array_keys($topics) ), sprintf( $this->lang->words['multi_post_delete'], implode( ', ', $_ids ) ) );
		
		return array_keys($topics);
	}
	
	/**
	 * Delete a post
	 *
	 * @param	mixed 		Post id | Array of post ids
	 * @param	bool		Skip rebuilding stats/forums
	 * @return	array		Topic IDs
	 */
	public function postDeleteFromDb( $id, $nostats=false )
	{
		$posts			= array();
		$attach_tid		= array();
		$attach_ids		= array();
		$topics			= array();
		$fids           = array();
		$this->error	= "";

		if ( is_array( $id ) )
		{
			$id = IPSLib::cleanIntArray( $id );
			
			if ( count($id) > 0 )
			{
				$pid = " IN(" . implode( ",", $id ) . ")";
			}
			else
			{
				return false;
			}
		}
		else
		{
			if ( intval($id) )
			{
				$pid   = "={$id}";
			}
			else
			{
				return false;
			}
		}
		
		/* Remove from deletion log */
		$_ids = ( is_array( $id ) ) ? $id : array( $id );
		IPSDeleteLog::removeEntries( $_ids, 'post', TRUE );
		
		//-----------------------------------------
		// Get Stuff
		//-----------------------------------------
		
		$this->DB->build( array( 'select' => 'pid, topic_id, new_topic', 'from' => 'posts', 'where' => 'pid' . $pid ) );
		$q = $this->DB->execute();
		
		while ( $r = $this->DB->fetch( $q ) )
		{
			$posts[ $r['pid'] ]			= $r['topic_id'];
			$topics[ $r['topic_id'] ]	= 1;
		}
		
		/* Delete from rep cache */
		$this->DB->delete( 'reputation_cache', "app='forums' AND type='pid' AND type_id" . $pid );
		$this->DB->delete( 'reputation_index', "app='forums' AND type='pid' AND type_id" . $pid );
		
		//-----------------------------------------
		// Is there an attachment to this post?
		//-----------------------------------------
		
		$classToLoad  = IPSLib::loadLibrary( IPSLib::getAppDir( 'core' ) . '/sources/classes/attach/class_attach.php', 'class_attach' );
		$class_attach = new $classToLoad( $this->registry );
		$class_attach->type = 'post';
		$class_attach->init();
		
		$class_attach->bulkRemoveAttachment( array_keys( $posts ) );
		
		//-----------------------------------------
		// delete the post
		//-----------------------------------------
		
		$this->DB->delete( 'posts', "pid" . $pid );
		
		/* Remove cache content */
		IPSContentCache::drop( 'post', array_keys( $posts ) );

		//-----------------------------------------
		// Update all relevant topics
		//-----------------------------------------
		
		foreach( array_keys($topics) as $tid )
		{
			$this->rebuildTopic( $tid );
		}
		
		//-----------------------------------------
		// Update the stats
		//-----------------------------------------
		
		if( !$nostats )
		{
			if ( count( $topics ) )
			{
				$this->DB->build( array( 'select' => 'forum_id',
										 'from'   => 'topics',
										 'where'  => 'tid IN (' . implode( ',', array_keys( $topics ) ) . ')' ) );
										 
				$i = $this->DB->execute();
				
				while( $row = $this->DB->fetch( $i ) )
				{
					$fids[ $row['forum_id'] ] = $row['forum_id'];
				}
			}
			
			if ( count( $fids ) )
			{
				foreach( $fids as $f )
				{
					$this->forumRecount( $f );
				}
			}
			
			$this->cache->rebuildCache( 'stats', 'global' );
		}
		
		/* Run moderation sync */
		$this->runModSync( 'postDelete', array_keys( $posts ) );
		
		//-----------------------------------------
		// Log and return
		//-----------------------------------------
		
		$pid = str_replace( array( 'IN', '(', ')', '=' ), '', $pid );
		
		$this->addModerateLog( "", "", "", $this->lang->words['mod_from_id'] . ' ' . implode( ',', array_keys($topics) ), sprintf( $this->lang->words['multi_post_delete'], implode( ',', $_ids ) ) );
		
		return array_keys($topics);
	}
	
	/**
	 * Rebuild a topic
	 *
	 * @param	integer 	Topic id
	 * @param 	boolean		Rebuild forum afterwards
	 * @return	boolean		Rebuild complete
	 */
	public function rebuildTopic( $tid, $doforum=1 )
	{
		/* Topic ID */
		$tid = intval( $tid );

		if( $this->settings['post_order_column'] != 'post_date' )
		{
			$this->settings[ 'post_order_column'] =  'pid' ;
		}
		
		if( $this->settings['post_order_sort'] != 'desc' )
		{
			$this->settings[ 'post_order_sort'] =  'asc' ;
		}
				
		//-----------------------------------------
		// Get the correct number of replies
		//-----------------------------------------
		
		$_queued	= $this->registry->class_forums->fetchPostHiddenQuery( array( 'visible' ), '' );
		$posts		= $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as posts', 'from' => 'posts', 'where' => "topic_id={$tid} and {$_queued}" ) );
		$pcount		= intval( $posts['posts'] - 1 );
		
		//-----------------------------------------
		// Get the correct number of queued replies
		//-----------------------------------------
		
		$_queued	= $this->registry->class_forums->fetchPostHiddenQuery( array( 'hidden' ), '' );
		$qposts		= $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as posts', 'from' => 'posts', 'where' => "topic_id={$tid} and {$_queued}" ) );
		$qpcount	= intval( $qposts['posts'] );
		
		//-----------------------------------------
		// Get the correct number of soft deleted replies
		//-----------------------------------------
		
		$_queued	= $this->registry->class_forums->fetchPostHiddenQuery( array( 'sdelete' ), '' );
		$dposts		= $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as posts', 'from' => 'posts', 'where' => "topic_id={$tid} and {$_queued}" ) );
		$dpcount	= intval( $dposts['posts'] );
		
		//-----------------------------------------
		// Get last post info
		//-----------------------------------------
		
		$_queued	= $this->registry->class_forums->fetchPostHiddenQuery( array( 'visible' ), 'p.' );
		
		$last_post	= $this->DB->buildAndFetch( array(
													'select'	=> 'p.post_date, p.topic_id, p.author_id, p.author_name, p.pid',
													'from'		=> array( 'posts' => 'p' ),
													'where'		=> 'p.topic_id=' . $tid . ' AND ' . $_queued,
													'order'		=>  $this->settings['post_order_column'] . ' DESC',
													'limit'		=> array( 1 ),
													'add_join'	=> array(
																		array( 'select'	=> 't.forum_id, t.title',
																				'from'	=> array( 'topics' => 't' ),
																				'where'	=> 't.tid=p.topic_id',
																				'type'	=> 'left'
																			),
																		array( 'select'	=> 'm.member_id, m.members_display_name',
																				'from'	=> array( 'members' => 'm' ),
																				'where'	=> 'm.member_id=p.author_id',
																				'type'	=> 'left'
																			),
																		)
											)		);

		$last_poster_name = $last_post['members_display_name'] ? $last_post['members_display_name'] : $last_post['author_name'];
		
		//-----------------------------------------
		// Get first post info
		//-----------------------------------------
		
		$first_post = $this->DB->buildAndFetch( array(
													'select'	=> 'p.post_date, p.author_id, p.author_name, p.pid',
													'from'		=> array( 'posts' => 'p' ),
													'where'		=> "p.topic_id={$tid}",
													'order'		=> "p." . $this->settings['post_order_column'] . " ASC",
													'limit'		=> array(0,1),
													'add_join'	=> array(
																		array(
																			'select'	=> 'm.member_id, m.members_display_name',
																			'from'		=> array( 'members' => 'm' ),
																			'where'		=> "p.author_id=m.member_id",
																			'type'		=> 'left'
																			)
																		)
											)		);

		$first_poster_name = $first_post['members_display_name'] ? $first_post['members_display_name'] : $first_post['author_name'];
		$_last_poster_name = $last_poster_name ? $last_poster_name : ( $pcount > 0 ? $this->lang->words['global_guestname'] : $first_poster_name );
		
		//-----------------------------------------
		// Get number of attachments
		//-----------------------------------------
		
		$attach = $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as count', 'from' => 'attachments', 'where' => "attach_parent_id={$tid} AND attach_rel_module='post'" ) );

		//-----------------------------------------
		// Update topic
		//-----------------------------------------
		
		$this->DB->setDataType( array( 'starter_name', 'last_poster_name' ), 'string' );

		$this->DB->update( 'topics', array( 
											'last_post'			  => intval($last_post['post_date'] ? $last_post['post_date'] : $first_post['post_date']),
											'last_poster_id'	  => intval($last_post['author_id'] ? $last_post['author_id'] : ( $pcount > 0 ? 0 : $first_post['author_id'] )),
											'last_poster_name'	  => $_last_poster_name,
											'topic_queuedposts'	  => intval($qpcount),
											'topic_deleted_posts' => intval($dpcount), 
											'posts'				  => intval($pcount),
											'starter_id'		  => intval($first_post['author_id']),
											'starter_name'		  => $first_poster_name,
											'seo_first_name'      => IPSText::makeSeoTitle( $first_poster_name ),
											'seo_last_name'       => IPSText::makeSeoTitle( $_last_poster_name ),
											'start_date'		  => intval($first_post['post_date']),
											'topic_firstpost'	  => intval($first_post['pid']),
											'topic_hasattach'	  => intval($attach['count']),
											'title_seo'			  => IPSText::makeSeoTitle( $last_post['title'] )
							), 'tid=' . $tid );

		//-----------------------------------------
		// Update first post
		//-----------------------------------------

		if( empty( $first_post['new_topic'] ) and $first_post['pid'] )
		{
			$this->DB->update( 'posts', array( 'new_topic' => 0 ), 'topic_id=' . $tid, true );
			$this->DB->update( 'posts', array( 'new_topic' => 1 ), 'pid=' . $first_post['pid'], true );
		}
		
		//-----------------------------------------
		// If we deleted the last post in a topic that was
		// the last post in a forum, best update that :D
		//-----------------------------------------
		
		if( ( $this->registry->class_forums->allForums[ $last_post['forum_id'] ]['last_id'] == $tid ) AND ( $doforum == 1 ) )
		{
			$tt = $this->DB->buildAndFetch( array(	
													'select'	=> 'title, tid, last_post, last_poster_id, last_poster_name',
													'from'		=> 'topics',
													'where'		=> 'forum_id=' . $last_post['forum_id'] . ' and ' . $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'visible' ), '' ),
													'order'		=> 'last_post desc',
													'limit'		=> array( 1 )
										)	);
			
			$dbs = array(
						 'last_title'		=> $tt['title']				? $tt['title']				: "",
						 'last_id'			=> $tt['tid']				? $tt['tid']				: 0,
						 'last_post'		=> $tt['last_post']			? $tt['last_post']			: 0,
						 'last_poster_name'	=> $tt['last_poster_name']	? $tt['last_poster_name']	: "",
						 'last_poster_id'	=> $tt['last_poster_id']	? $tt['last_poster_id']		: 0,
						 'seo_last_title'   => IPSText::makeSeoTitle( $tt['title'] ),
						 'seo_last_name'    => IPSText::makeSeoTitle( $tt['last_poster_name'] ),
						);
			
			if( $this->registry->class_forums->allForums[ $this->forum['id'] ]['newest_id'] == $tid )
			{
				$sort_key = $this->registry->class_forums->allForums[ $this->forum['id'] ]['sort_key'];
				
				$tt = $this->DB->buildAndFetch( array( 
														'select' => 'title, tid',
														'from'	 => 'topics',
														'where'	 => 'forum_id=' . $this->forum['id'] . ' and ' . $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'visible' ), '' ),
														'order'	 => 'start_date desc',
														'limit'	 => array( 1 )
											)	);
															
				$dbs['newest_id']		= $tt['tid']	? $tt['tid']	: 0;
				$dbs['newest_title']	= $tt['title']	? $tt['title']	: "";
				$dbs['seo_last_title']  = IPSText::makeSeoTitle( $tt['title'] );
			}
			
			$this->DB->setDataType( array( 'last_poster_name', 'seo_last_title', 'seo_last_name', 'last_title', 'newest_title' ), 'string' );

			$this->DB->update( 'forums', $dbs, "id=" . intval($this->forum['id']) );
		}

		return true;
	}
		
	/**
	 * Add a reply to a topic
	 *
	 * @param	string 		Post contnet
	 * @param 	array		Array of topic ids to apply this reply to
	 * @param	boolean		Increment post count?
	 * @return	boolean		Reply added
	 */
	public function topicAddReply($post="", $tids=array(), $incpost=0)
	{
		if ( $post == "" )
		{
			return false;
		}
		
		if ( count( $tids ) < 1 )
		{
			return false;
		}
		
		$post = array(
						'author_id'			=> $this->memberData['member_id'],
						'use_sig'			=> 1,
						'use_emo'			=> 1,
						'ip_address'		=> $this->member->ip_address,
						'post_date'			=> time(),
						'icon_id'			=> 0,
						'post'				=> $post,
						'author_name'		=> $this->memberData['members_display_name'],
						'topic_id'			=> "",
						'queued'			=> 0,
						'post_htmlstate'	=> 2,
					 );

		//-----------------------------------------
		// Add posts...
		//-----------------------------------------
		 
		$seen_fids		= array();
		$add_posts		= 0;
		
		foreach( $tids as $row )
		{
			$tid	= intval($row[0]);
			$fid	= intval($row[1]);
			$pa		= array();
			$ta		= array();
			
			if ( ! in_array( $fid, $seen_fids ) )
			{
				$seen_fids[] = $fid;
			}
			
			if ( $tid and $fid )
			{
				$pa				= $post;
				$pa['topic_id']	= $tid;
				
				$this->DB->insert( 'posts', $pa );
				
				$_pid = $this->DB->getInsertId();
				
				/* Add to cache */
				IPSContentCache::update( $_pid, 'post', $post['post'], FALSE );

				$ta = array (
							  'last_poster_id'		=> $this->memberData['member_id'],
							  'last_poster_name'	=> $this->memberData['members_display_name'],
							  'seo_last_name'       => IPSText::makeSeoTitle( $this->memberData['members_display_name'] ),
							  'last_post'			=> $pa['post_date'],
							);
				
				$this->DB->setDataType( array( 'last_poster_name', 'seo_last_name' ), 'string' );

				$this->DB->buildAndFetch( array( 'update' => 'topics', 'set' => $this->DB->compileUpdateString( $ta ) . ", posts=posts+1", 'where' => 'tid='.$tid ) );

				$add_posts++;
				
				//-----------------------------------------
				// Mark as read for current viewer
				//-----------------------------------------
				
				$this->registry->classItemMarking->markRead( array( 'forumID' => $fid, 'itemID' => $tid ) );
			}
		}

		if ( $add_posts > 0 )
		{
			$this->cache->rebuildCache( 'stats', 'global' );

			//-----------------------------------------
			// Update current members stuff
			//-----------------------------------------
		
			$pcount				= "";
			$member_group_id	= "";
			
			
			if ( ($this->forum['inc_postcount']) and ($incpost != 0) )
			{
				//-----------------------------------------
				// Increment the users post count
				//-----------------------------------------
				
				$pcount = "posts=posts+" . $add_posts . ", ";
				
				//-----------------------------------------
				// Are we checking for auto promotion?
				//-----------------------------------------
				
				if ($this->memberData['g_promotion'] != '-1&-1')
				{
					list($gid, $gposts)	= explode( '&', $this->memberData['g_promotion'] );
					
					if ( $gid > 0 and $gposts > 0 )
					{
						if ( $this->memberData['posts'] + $add_posts >= $gposts )
						{
							$member_group_id = "member_group_id='{$gid}', ";
						}
					}
				}
			}

			$this->DB->buildAndFetch( array( 'update' => 'members', 'set' => $pcount . $member_group_id . "last_post=" . time(), 'where' => "member_id=" . $this->memberData['member_id'] ) );
		}		
		
		return true;
	}
	
	/**
	 * Close a topic
	 *
	 * @param	integer 	Topic id
	 * @return	boolean
	 */
	public function topicClose( $id )
	{
		$this->stmInit();
		$this->stmAddClose();
		$this->stmExec($id);
		return TRUE;
	}
	
	
	/**
	 * Open a topic
	 *
	 * @param	integer 	Topic id
	 * @return	boolean
	 */
	public function topicOpen($id)
	{
		$this->stmInit();
		$this->stmAddOpen();
		$this->stmExec($id);
		return TRUE;
	}
	
	/**
	 * Pin a topic
	 *
	 * @param	integer 	Topic id
	 * @return	boolean
	 */
	public function topicPin($id)
	{
		$this->stmInit();
		$this->stmAddPin();
		$this->stmExec($id);
		return TRUE;
	}
	
	/**
	 * Unpin a topic
	 *
	 * @param	integer 	Topic id
	 * @return	boolean
	 */
	public function topicUnpin($id)
	{
		$this->stmInit();
		$this->stmAddUnpin();
		$this->stmExec($id);
		return TRUE;
	}
	
	/**
	 * Delete a topic
	 *
	 * @param	mixed 		Topic id | Array of topic ids
	 * @param	boolean		Skip updating the stats
	 * @return	boolean
	 */
	public function topicDelete( $id, $nostats=0 )
	{
		$posts			= array();
		$attach			= array();
		$this->error	= "";
		$ids            = array();
		
		if ( is_array( $id ) )
		{
			$id = IPSLib::cleanIntArray( $id );
			
			if ( count($id) > 0 )
			{
				$ids = $id;
				$tid = " IN(" . implode( ",", $id ) . ")";
			}
			else
			{
				return false;
			}
		}
		else
		{
			if ( intval($id) )
			{
				$tid   = "={$id}";
				$ids   = array( $id );
			}
			else
			{
				return false;
			}
		}
		
		/* Set ids */
		$_ids = ( is_array( $id ) ) ? $id : array( $id );
		
		/* Update the posts to delete */
		$this->DB->update( 'topics', array( 'approved' => $this->registry->class_forums->fetchTopicHiddenFlag('pdelete'), 'tdelete_time' => IPS_UNIX_TIME_NOW ), 'tid' . $tid );
		
		/* Tagging */
		$this->registry->tags->updateVisibilityByMetaId( $ids, 0 );

		/* Likes */
		require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
		$_like	= classes_like::bootstrap( 'forums', 'topics' );
		$_like->toggleVisibility( $ids, false );
		
		//-----------------------------------------
		// Recount forum...
		//-----------------------------------------
		
		if ( !$nostats )
		{
			if ( $this->forum['id'] )
			{
				$this->registry->class_forums->allForums[ $this->forum['id'] ]['_update_deletion'] = 1;
				$this->forumRecount( $this->forum['id'] );
			}
			
			$this->cache->rebuildCache( 'stats', 'global' );
		}
		
		/* Run moderation sync */
		$this->runModSync( 'topicDelete', $_ids );

		return TRUE;
	}
	
	/**
	 * Delete a topic
	 *
	 * @param	mixed 		Topic id | Array of topic ids
	 * @param	boolean		Skip updating the stats
	 * @return	boolean
	 */
	public function topicDeleteFromDB( $id, $nostats=0 )
	{
		$posts			= array();
		$attach			= array();
		$this->error	= "";
		$ids            = array();
		
		if ( is_array( $id ) )
		{
			$id = IPSLib::cleanIntArray( $id );
			
			if ( count($id) > 0 )
			{
				$ids = $id;
				$tid = " IN(" . implode( ",", $id ) . ")";
			}
			else
			{
				return false;
			}
		}
		else
		{
			if ( intval($id) )
			{
				$ids = array( $id );
				$tid   = "={$id}";
			}
			else
			{
				return false;
			}
		}
		
		/* Remove from deletion log */
		IPSDeleteLog::removeEntries( $ids, 'topic', TRUE );
		
		/* Tagging */
		$this->registry->tags->deleteByMetaId( $ids );
		
		//-----------------------------------------
		// Remove polls assigned to this topic
		//-----------------------------------------
		
		$this->DB->delete( 'polls', "tid" . $tid );
		$this->DB->delete( 'voters', "tid" . $tid );
		$this->DB->delete( 'topic_ratings', "rating_tid" . $tid );	
		$this->DB->delete( 'topic_views', "views_tid" . $tid );
		$this->DB->delete( 'topics', "tid" . $tid );

		//-----------------------------------------
		// Like class
		//-----------------------------------------
		
		require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
		$_like	= classes_like::bootstrap( 'forums', 'topics' );
		$_like->remove( $ids );
		
		//-----------------------------------------
		// Get PIDS for attachment deletion
		//-----------------------------------------
		
		$this->DB->build( array( 'select' => 'pid', 'from' => 'posts', 'where' => "topic_id" . $tid ) );
		$this->DB->execute();
		
		while ( $r = $this->DB->fetch() )
		{
			$posts[] = $r['pid'];
		}
		
		/* Remove cache content */
		IPSContentCache::drop( 'post', $posts );
		
		//-----------------------------------------
		// Remove the attachments
		//-----------------------------------------
		
		$classToLoad  = IPSLib::loadLibrary( IPSLib::getAppDir( 'core' ) . '/sources/classes/attach/class_attach.php', 'class_attach' );
		$class_attach = new $classToLoad( $this->registry );
		$class_attach->type = 'post';
		$class_attach->init();
		
		$class_attach->bulkRemoveAttachment( $posts );		
		
		//-----------------------------------------
		// Remove the posts
		//-----------------------------------------
		
		$this->DB->delete( 'posts', "topic_id" . $tid );
		$this->DB->delete( 'reputation_cache', "app='forums' AND type='pid' AND type_id " . $tid );
		$this->DB->delete( 'reputation_index', "app='forums' AND type='pid' AND type_id " . $tid );
		
		//-----------------------------------------
		// Recount forum...
		//-----------------------------------------
		
		if ( !$nostats )
		{
			if ( $this->forum['id'] )
			{
				$this->registry->class_forums->allForums[ $this->forum['id'] ]['_update_deletion'] = 1;
				$this->forumRecount( $this->forum['id'] );
			}
			
			$this->cache->rebuildCache( 'stats', 'global' );
		}
		
		/* Run moderation sync */
		$this->runModSync( 'topicDelete', $ids );

		return TRUE;
	}

	/**
	 * Move a topic
	 *
	 * @param	mixed 		Topic id | Array of topic ids
	 * @param	integer		Source forum
	 * @param	integer		Move to forum
	 * @param	boolean		Leave the 'link'
	 * @return	boolean
	 */
	public function topicMove($topics, $source=0, $moveto=0, $leavelink=0)
	{
		$this->error	= "";
		$source			= intval($source);
		$moveto			= intval($moveto);
		$forumIDSQL     = ( $source ) ? " forum_id={$source} AND " : '';
		$ids            = array();
		
		if ( is_array( $topics ) )
		{
			$topics = IPSLib::cleanIntArray( $topics );
			
			if ( count($topics) > 0 )
			{
				$tid = " IN(" . implode( ",", $topics ) . ")";
				$ids = $topics;
			}
			else
			{
				return false;
			}

			//-----------------------------------------
			// Mark as read in new forum
			//-----------------------------------------

			foreach( $topics as $_tid )
			{	
				$this->registry->classItemMarking->markRead( array( 'forumID' => $moveto, 'itemID' => $_tid ), 'forums' );
			}
		}
		else
		{
			if ( intval($topics) )
			{
				$tid   = "={$topics}";
				$ids   = array( $topics );
			}
			else
			{
				return false;
			}
			
			//-----------------------------------------
			// Mark as read in new forum
			//-----------------------------------------
			
			$this->registry->classItemMarking->markRead( array( 'forumID' => $moveto, 'itemID' => $topics ), 'forums' );
		}
		
		//-----------------------------------------
		// Update the topic
		//-----------------------------------------
		
		$this->DB->update( 'topics', array( 'forum_id' => $moveto ), $forumIDSQL . "tid" . $tid );
		
		//-----------------------------------------
		// Update the polls
		//-----------------------------------------
		
		$this->DB->update( 'polls', array( 'forum_id' => $moveto ), $forumIDSQL . "tid" . $tid );
		
		//-----------------------------------------
		// Are we leaving a stink er link?
		//-----------------------------------------
		
		if ( $leavelink AND $source )
		{
			$this->DB->build( array( 'select' => '*', 'from' => 'topics', 'where' => "tid" . $tid ) );
			$oq = $this->DB->execute();

			while ( $row = $this->DB->fetch($oq) )
			{
				$this->DB->setDataType( array( 'title', 'starter_name', 'last_poster_name', 'seo_last_name' ), 'string' );

				$this->DB->insert( 'topics', array (
														'title'				=> $row['title'],
														'state'				=> 'link',
														'posts'				=> 0,
														'views'				=> 0,
														'starter_id'		=> $row['starter_id'],
														'start_date'		=> $row['start_date'],
														'starter_name'		=> $row['starter_name'],
														'seo_first_name'    => IPSText::makeSeoTitle( $row['starter_name'] ),
														'last_post'			=> $row['last_post'],
														'forum_id'			=> $source,
														'approved'			=> 1,
														'pinned'			=> 0,
														'moved_to'			=> $row['tid'] . '&' . $moveto,
														'moved_on'			=> time(),
														'last_poster_id'	=> $row['last_poster_id'],
														'last_poster_name'	=> $row['last_poster_name'],
														'seo_last_name'     => IPSText::makeSeoTitle( $row['starter_name'] ),
									)				);
			}
		
		}
		
		/* Tagging */
		$this->registry->tags->moveTagsToParentId( $ids, $moveto );
		
		//-----------------------------------------
		// Sort out subscriptions
		//-----------------------------------------
		
		$trid_to_delete	= array();

		//-----------------------------------------
		// Like class - remove if you don't have access
		//-----------------------------------------
		
		require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
		$_like	= classes_like::bootstrap( 'forums', 'topics' );
		$topics	= $_like->getDataByRelationshipId( $tid );
		
		if( count($topics) )
		{
			foreach( $topics as $r )
			{
				$perm_id	= $r['org_perm_id'] ? $r['org_perm_id'] : $r['g_perm_id'];
				
				if ( $this->registry->permissions->check( 'read', $this->registry->class_forums->allForums[ $moveto ], explode( ',', IPSText::cleanPermString( $perm_id ) ) ) !== TRUE )
				{
					$trid_to_delete[]	= $r['like_id'];
				}
			}
		}

		if ( count($trid_to_delete) > 0 )
		{
			$this->DB->delete( 'core_like', "like_id IN('" . implode( "','", $trid_to_delete ) . "')" );
		}
		
		return true;
	}
	
	/**
	 * Delete / undelete posts
	 *
	 * @param	array 		Array of Post IDs
	 * @param	boolean		Approve (TRUE) / Unapprove (FALSE)
	 * @return	boolean
	 */
	public function topicToggleSoftDelete( $topicIds, $delete=FALSE, $reason='' )
	{
		//-----------------------------------------
		// INIT
		//-----------------------------------------
		
		$_restoreTopic	= 1;
		
		if ( $delete === TRUE )
		{
			$_restoreTopic = -1;
		}
		
		$_topics	= array();
		$_forumIDs	= array();
		$_tids		= IPSLib::cleanIntArray( $topicIds );
		
		//-----------------------------------------
		// Fetch distinct topic IDs
		//-----------------------------------------
		
		$this->DB->build( array( 'select' => '*',
								 'from'   => 'topics',
								 'where'  => 'tid IN (' . implode( ',', $_tids ) . ')' ) );
								
		$this->DB->execute();
		
		while( $row = $this->DB->fetch() )
		{
			$_topics[ $row['tid'] ]   = $row;
			$_forumIDs[ $row['forum_id'] ] = $row['forum_id'];
		}

		//-----------------------------------------
		// Did we get the first post too?
		//-----------------------------------------
		
		foreach( $_topics as $tid => $topicData )
		{
			$this->DB->update( 'topics', array( 'approved' => $_restoreTopic ), 'tid=' . $tid );
			
			if ( $delete )
			{
				IPSDeleteLog::addEntry( $tid, 'topic', $reason, $this->memberData );
			}
			
			/* Rebuild topic */
			$this->rebuildTopic( $tid );
			
			/* Mod log */
			$this->addModerateLog( $topicData['forum_id'], $tid, 0, $topicData['title'], sprintf( $this->lang->words['acp_altered_topics'], "approved={$_restoreTopic}", $topicData['title'] ) );
		}
		
		/* Restoring */
		if ( ! $delete )
		{
			/* Un-deleting, so delete */
			IPSDeleteLog::removeEntries( $_tids, 'post' );
		}
		
		if ( count( $_forumIDs ) )
		{
			foreach( $_forumIDs as $_fid )
			{
				$this->forumRecount( $_fid );
			}
		}
		
		/* Tagging */
		$this->registry->tags->updateVisibilityByMetaId( $_tids, ( $delete ? 0 : 1 ) );

		/* Likes */
		require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
		$_like	= classes_like::bootstrap( 'forums', 'topics' );
		$_like->toggleVisibility( $_tids, $delete );
		
		$this->cache->rebuildCache( 'stats', 'global' );
		
		return TRUE;
	}

	/**
	 * Recount a forum
	 *
	 * @return	boolean
	 */
	public function forumRecount( $fid="" )
	{
		$fid	= $fid ? intval($fid) : $this->forum['id'];
		
		if ( ! $fid )
		{
			return false;
		}
		
		//-----------------------------------------
		// Get the topics..
		//-----------------------------------------
		
		$topics			= $this->DB->buildAndFetch( array( 'select'	=> 'COUNT(*) as count',
														   'from'	=> 'topics',
														   'where'	=> $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'visible' ), '' ) . " and forum_id={$fid}" ) );
		
		//-----------------------------------------
		// Get the QUEUED topics..
		//-----------------------------------------
		
		$queued_topics	= $this->DB->buildAndFetch( array( 'select'	=> 'COUNT(*) as count',
																	'from'	=> 'topics',
																	'where'	=> $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'hidden' ), '' ) . " and forum_id={$fid}" ) );
																	
		//-----------------------------------------
		// Get the DELETED topics..
		//-----------------------------------------
		
		$deleted_topics	= $this->DB->buildAndFetch( array( 'select'	=> 'COUNT(*) as count',
														   'from'	=> 'topics',
														   'where'	=> "approved=-1 and forum_id={$fid}" ) );
		
		//-----------------------------------------
		// Get the posts..
		//-----------------------------------------
		
		$posts			= $this->DB->buildAndFetch( array( 'select'	=> 'SUM(posts) as replies',
																	'from'	=> 'topics',
																	'where'	=> $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'visible' ), '' ) . " and forum_id={$fid}" ) );
		
		//-----------------------------------------
		// Get the QUEUED posts..
		//-----------------------------------------
		
		$queued_posts	= $this->DB->buildAndFetch( array( 'select'	=> 'SUM(topic_queuedposts) as replies',
																	'from'	=> 'topics',
																	'where'	=> $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'visible', 'hidden' ), '' ) . " and forum_id={$fid}" ) );
		
		//-----------------------------------------
		// Get the DELETED posts..
		//-----------------------------------------
												
		$deleted_posts	= $this->DB->buildAndFetch( array( 'select'	=> 'SUM(topic_deleted_posts) as replies',
																	'from'	=> 'topics',
																	'where'	=> "forum_id={$fid}" ) );
		
		//-----------------------------------------
		// Get the forum last poster..
		//-----------------------------------------
		
		$last_post		= $this->DB->buildAndFetch( array( 'select'	=> 'tid, title, last_poster_id, last_poster_name, seo_last_name, last_post',
																	'from'	=> 'topics',
																	'where'	=> $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'visible' ), '' ) . " and forum_id={$fid}",
																	'order'	=> 'last_post DESC',
																	'limit'	=> array( 1 ) 
														)		);
		
		$newest_topic	= $this->DB->buildAndFetch( array( 'select'	=> 'title, tid, seo_first_name',
																	'from'	=> 'topics',
																	'where'	=> 'forum_id=' . $fid . ' and ' . $this->registry->getClass('class_forums')->fetchTopicHiddenQuery( array( 'visible' ), '' ),
																	'order'	=> 'start_date desc',
																	'limit'	=> array( 1 )
															)	  );
		
		$lastXTopics    = $this->registry->class_forums->lastXFreeze( $this->registry->class_forums->buildLastXTopicIds( $fid, FALSE ) );
		
		//-----------------------------------------
		// Reset this forums stats
		//-----------------------------------------
		
		$dbs = array(
					  'name_seo'			=> IPSText::makeSeoTitle( $this->registry->class_forums->allForums[ $fid ]['name'] ),
					  'last_poster_id'		=> intval($last_post['last_poster_id']),
					  'last_poster_name'	=> $last_post['last_poster_name'],
					  'seo_last_name'       => IPSText::makeSeoTitle( $last_post['last_poster_name'] ),
					  'last_post'			=> intval($last_post['last_post']),
					  'last_title'			=> $last_post['title'],
					  'seo_last_title'      => IPSText::makeSeoTitle( $last_post['title'] ),
					  'last_id'				=> intval($last_post['tid']),
					  'topics'				=> intval($topics['count']),
					  'posts'				=> intval($posts['replies']),
					  'queued_topics'		=> intval($queued_topics['count']),
					  'queued_posts'		=> intval($queued_posts['replies']),
					  'deleted_posts'		=> intval($deleted_posts['replies']),
					  'deleted_topics'		=> intval($deleted_topics['count']),
					  'newest_id'			=> intval($newest_topic['tid']),
					  'newest_title'		=> $newest_topic['title'],
					  'last_x_topic_ids'    => $lastXTopics
					);
					
		if ( $this->registry->class_forums->allForums[ $fid ]['_update_deletion'] )
		{
			$dbs['forum_last_deletion'] = time();
		}
		
		$this->DB->setDataType( array( 'last_poster_name', 'last_title', 'newest_title', 'seo_last_title', 'seo_last_name' ), 'string' );

		$this->DB->update( 'forums', $dbs, "id=" . $fid );

		return true;
	}
	
	
	/**
	 * Multi-statement init
	 *
	 * @return	boolean
	 */
	public function stmInit()
	{
		$this->stm	= array();
		
		return true;
	}
	
	/**
	 * Multi-statement execute
	 *
	 * @param 	mixed		Id | Array of ids
	 * @return	boolean
	 */
	public function stmExec($id)
	{
		if ( count($this->stm) < 1 )
		{
			return false;
		}
		
		$final_array = array();
		
		foreach( $this->stm as $real_array )
		{
			foreach( $real_array as $k => $v )
			{
				$final_array[ $k ] = $v;
			}
		}

		if ( is_array($id) )
		{
			$id = IPSLib::cleanIntArray( $id );
			
			if ( count($id) > 0 )
			{
				$this->DB->update( 'topics', $final_array, "tid IN(" . implode( ",", $id ) . ")" );
			}
			else
			{
				return false;
			}
		}
		else if ( intval($id) != "" )
		{
			$this->DB->update( 'topics', $final_array, "tid=" . intval($id) );
		}
		else
		{
			return false;
		}
		
		return true;
	}
	
	
	/**
	 * Multi-statement pin topic
	 *
	 * @return	boolean
	 */
	public function stmAddPin()
	{
		$this->stm[] = array( 'pinned' => 1 );
		
		return TRUE;
	}
	
	/**
	 * Multi-statement unpin topic
	 *
	 * @return	boolean
	 */
	public function stmAddUnpin()
	{
		$this->stm[] = array( 'pinned' => 0 );
		
		return TRUE;
	}
	
	/**
	 * Multi-statement close topic
	 *
	 * @return	boolean
	 */
	public function stmAddClose()
	{
		$this->stm[] = array( 'state' => 'closed' );
		$this->stm[] = array( 'topic_open_time' => 0 );
		$this->stm[] = array( 'topic_close_time' => 0 );
		
		return TRUE;
	}
	
	/**
	 * Multi-statement open topic
	 *
	 * @return	boolean
	 */
	public function stmAddOpen()
	{
		$this->stm[] = array( 'state' => 'open' );
		$this->stm[] = array( 'topic_open_time' => 0 );
		$this->stm[] = array( 'topic_close_time' => 0 );
				
		return TRUE;
	}
	
	/**
	 * Multi-statement update topic title
	 *
	 * @return	boolean
	 */
	public function stmAddTitle($new_title='')
	{
		if ( $new_title == "" )
		{
			return FALSE;
		}
		
		$this->stm[] = array( 'title' => $new_title );
		
		return TRUE;
	}
	
	/**
	 * Multi-statement approve topic
	 *
	 * @return	boolean
	 */
	public function stmAddApprove()
	{
		$this->stm[] = array( 'approved' => 1 );
		
		return TRUE;
	}
	
	/**
	 * Multi-statement unapprove topic
	 *
	 * @return	boolean
	 */
	public function stmAddUnapprove()
	{
		$this->stm[] = array( 'approved' => 0 );
		
		return TRUE;
	}
	
	/**
	 * Create 'where' clause for SQL forum pruning
	 *
	 * @return	boolean
	 */
	public function sqlPruneCreate( $forum_id, $starter_id="", $topic_state="", $post_min="", $date_exp="", $ignore_pin="" )
	{
		$sql = 'forum_id=' . intval($forum_id) . ' AND approved < 2';

		if ( intval($date_exp) )
		{
			$sql .= " AND last_post < " . intval( $date_exp );
		}
		
		if ( intval($starter_id) )
		{
			$sql .= " AND starter_id=" . intval( $starter_id );
			
		}
		
		if ( intval($post_min) )
		{
			$sql .= " AND posts < " . intval( $post_min );
		}
		
		if ($topic_state != 'all')
		{
			if ($topic_state)
			{
				$sql .= " AND state='{$topic_state}'";
			}
		}
		
		if ( $ignore_pin != "" )
		{
			$sql .= " AND pinned=0";
		}

		return $sql;
	}
	
	/**
	 * Determines if current member is authorized to use multi-mod
	 *
	 * @return	boolean
	 */
	public function mmAuthorize()
	{
		$pass_go = FALSE;
		
		if ( $this->memberData['member_id'] )
		{
			if ( $this->memberData['g_is_supmod'] )
			{ 
				$pass_go = TRUE;
			}
			else if ( $this->moderator['can_mm'] == 1 )
			{
				$pass_go = TRUE;
			}
		}
		
		return $pass_go;
	}
	
	/**
	 * Checks if multi-mod is allowed in a specified forum
	 *
	 * @return	boolean
	 */
	public function mmCheckIdInForum( $fid, $mm_data)
	{
		$retval = FALSE;
		
		if (  $mm_data['mm_forums'] == '*' OR strstr( "," . $mm_data['mm_forums'] . ",", "," . $fid . "," ) )
		{
			$retval = TRUE;
		}
		
		return $retval;	
	}
	
	/**
	 * Add an entry to the moderator log
	 *
	 * @param	integer		Forum id
	 * @param	integer		Topic id
	 * @param	string		Topic title
	 * @param	string		Title to add to moderator log
	 * @return	boolean
	 */
	public function addModerateLog($fid, $tid, $pid, $t_title, $mod_title='Unknown')
	{
		$this->DB->setDataType( 'member_name', 'string' );
		
		$this->DB->insert( 'moderator_logs', array (
												  'forum_id'		=> intval($fid),
												  'topic_id'		=> intval($tid),
												  'post_id'			=> intval($pid),
												  'member_id'		=> $this->memberData['member_id'],
												  'member_name'		=> $this->memberData['members_display_name'],
												  'ip_address'		=> $this->member->ip_address,
												  'http_referer'	=> htmlspecialchars( my_getenv('HTTP_REFERER') ),
												  'ctime'			=> time(),
												  'topic_title'		=> $t_title,
												  'action'			=> $mod_title,
												  'query_string'	=> htmlspecialchars( my_getenv('QUERY_STRING') ),
											  )  );
		return TRUE;
	}
	
	/**
	 * Run the forum mod sync system
	 *
	 * @param	string		Function to call
	 * @return	bool
	 */
	public function runModSync( $func )
	{
		/* ipsRegistry::$applications only contains apps with a public title #15785 */
		$app_cache = $this->registry->cache()->getCache('app_cache');
		
		/* Params */
		$params = func_get_args();
		array_shift( $params );

		/* Loop through applications */
		foreach( $app_cache as $app_dir => $app )
		{
			if( IPSLib::appIsInstalled( $app_dir ) )
			{
				/* Setup */
				$_file  = IPSLib::getAppDir( $app['app_directory'] ) . '/extensions/forumDataSync.php';
					
				/* Check for the file */
				if( is_file( $_file ) )
				{
					/* Get the file */
					$classToLoad = IPSLib::loadLibrary( $_file, $app['app_directory'] . '_forumDataSync', $app['app_directory'] );
					
					/* Check for the class */
					if( class_exists( $classToLoad ) )
					{
						/* Create an object */
						$_obj = new $classToLoad( $this->registry );
	
						/* Check for the module */
						if( method_exists( $_obj, $func ) )
						{
							/* Call it */
							call_user_func_array( array( $_obj, $func ), $params );
						}
					}
				}
			}
		}
	}
}