<?php
/**
 * <pre>
 * Invision Power Services
 * IP.Board v3.4.6
 * Moderator actions
 * Last Updated: $Date: 2012-06-29 10:12:14 -0400 (Fri, 29 Jun 2012) $
 * </pre>
 *
 * @author 		$Author: bfarber $
 * @copyright	(c) 2001 - 2009 Invision Power Services, Inc.
 *
 * @package		IP.Board
 * @subpackage	Forums
 *
 * @version		$Revision: 11007 $
 *
 */

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();
		}
		
		/* Init */
		if ( ! $this->registry->isClassLoaded('topics') )
		{
			$classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'forums' ) . "/sources/classes/topics.php", 'app_forums_classes_topics', 'forums' );
			$this->registry->setClass( 'topics', new $classToLoad( $this->registry ) );
		}
		
		/* 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();
		$followTids = 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'];
				$followTids[ $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( $topicIDs, ( $approve === TRUE ? 1 : 0 ) );
			
			/* Likes */
			if ( count( $followTids ) )
			{
				require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
				$_like	= classes_like::bootstrap( 'forums', 'topics' );
				$_like->toggleVisibility( $followTids, $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'    => 'tid DESC',
									 'limit'    => array( 0, 2000 ) ) );
			$this->DB->execute();
			
			while( $row = $this->DB->fetch() )
			{
				$tagTopicIDs[] = $row['tid'];
			}
			
			if ( count( $tagTopicIDs ) )
			{
				/* Tagging */
				try
				{
					$this->registry->tags->updateVisibilityByMetaId( $tagTopicIDs, 0 );
				}
				catch ( Exception $e ) { }
				
				/* 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;
			}
			
			if ( $this->registry->getClass('topics')->isArchived( $topicData ) )
			{
				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 )
		{
			/* Delete from recent posts */
			$this->registry->topics->deleteRecentPost( array( 'post_id' => $_pids ) );
		
			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'] ) );
			}
			
			/* Run moderation sync */
			$this->runModSync( 'postHide', $_pids );
		}
		else
		{
			/* Restore from recent posts */
			$this->registry->topics->restoreRecentPost( array( 'post_id' => $_pids ) );
			
			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'] ) );
			}
			
			/* Run moderation sync */
			$this->runModSync( 'postUnhide', $_pids );
		}
		
		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' ) . ', p.author_id, p.author_name, p.post',
								 '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;
			}
			
			if ( $this->registry->getClass('topics')->isArchived( $topicData ) )
			{
				continue;
			}
			
			$_forumIDs[] = $topicData['forum_id'];
			
			if ( in_array( $topicData['topic_firstpost'], $_pids ) )
			{
				$this->DB->update( 'topics', array( 'approved' => $_approveTopic ), 'tid=' . $tid );
				
				/* Need to hide/show tags too */
				$this->registry->tags->updateVisibilityByMetaId( $tid, $_approveTopic );
			
				/* 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.---.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 )
		{
			$this->registry->getClass( 'class_localization')->loadLanguageFile( array( 'public_email_content' ), 'core' );
		
			/* Delete from recent posts */
			$this->registry->topics->restoreRecentPost( array( 'post_id' => $_pids ) );
			
			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'] ) );
				
				/* Notifications are sent in clearModQueueTable now */
			}
			
			if( count($_pids) )
			{
				$this->clearModQueueTable( 'post', $_pids, true );
			}
			
			if( count($_tids) )
			{
				$this->clearModQueueTable( 'topic', $_tids, true );
			}
		}
		else
		{
			/* Delete from recent posts */
			$this->registry->topics->deleteRecentPost( array( 'post_id' => $_pids ) );
			
			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 )
	{
		//-----------------------------------------
		// 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
		{
			//-----------------------------------------
			// 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 );
			
			//-----------------------------------------
			// 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.*',
																'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, $r['post'] );
										
					$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.*',
																'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( $this->registry->class_forums->getForumById( $r['forum_id'] ), $r, $r['post'] );
										
					$this->DB->delete( 'mod_queued_items', 'id=' . $r['id'] );
				}
				
			}
		}
	}

	/**
	 * 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 );
		
		/* Get data */
		$this->DB->build( array( 'select' => 'p.pid, p.topic_id, p.new_topic',
								 'from'   => array( 'posts' => 'p' ),
								 'where'  => 'p.pid' . $pid,
								 'add_join' => array( array( 'select' => 't.*',
															 'from'   => array('topics' => 't' ),
															 'where'  => 'p.topic_id=t.tid' ) ) ) );
		$q = $this->DB->execute();
		
		while ( $r = $this->DB->fetch( $q ) )
		{
			if ( ! $this->registry->getClass('topics')->isArchived( $r ) )
			{
				$posts[ $r['pid'] ]			= $r['topic_id'];
				$topics[ $r['topic_id'] ]	= 1;
			}
		}
		
		/* Reset keys */
		$pid = " IN(" . implode( ",", array_keys( $posts ) ) . ")";
			
		/* 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 );
		
		/* Delete from recent posts */
		$this->registry->topics->deleteRecentPost( array( 'post_id' => $_ids ) );
		
		//-----------------------------------------
		// 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
	 * @param	bool		Quick mode (will skip loading data and rebuild - only use if you are processing many posts and will rebuild manually later)
	 * @return	array		Topic IDs
	 */
	public function postDeleteFromDb( $id, $nostats=false, $quick=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
		//-----------------------------------------
		
		/* Get IDs */
		if ( $quick )
		{
			$posts = array_flip( $_ids );
		}
		else
		{
			$this->DB->build( array( 'select' => 'p.pid, p.topic_id, p.new_topic',
									 'from'   => array( 'posts' => 'p' ),
									 'where'  => 'p.pid' . $pid,
									 'add_join' => array( array( 'select' => 't.*',
																 'from'   => array('topics' => 't' ),
																 'where'  => 'p.topic_id=t.tid' ) ) ) );
			$q = $this->DB->execute();
			
			while ( $r = $this->DB->fetch( $q ) )
			{
				if ( ! $this->registry->getClass('topics')->isArchived( $r ) )
				{
					$posts[ $r['pid'] ]			= $r['topic_id'];
					$topics[ $r['topic_id'] ]	= 1;
				}
			}
		}
		
		/* Reset keys */
		$pid = " IN(" . implode( ",", array_keys( $posts ) ) . ")";
		
		/* 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 );
		
		/* And totals */
		foreach( array_keys($posts) as $post )
		{
			$this->DB->delete( 'reputation_totals', "rt_key=MD5('forums;pid;" . $post . "') AND rt_type_id=" . $post );
		}
		
		/* Delete from recent posts */
		$this->registry->topics->deleteRecentPost( array( 'post_id' => array_keys( $posts ) ) );
		
		//-----------------------------------------
		// 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
		//-----------------------------------------
		
		if ( !$quick )
		{
			foreach( array_keys($topics) as $tid )
			{
				$this->rebuildTopic( $tid );
			}
		}
		
		//-----------------------------------------
		// Update the stats
		//-----------------------------------------
		
		if( !$nostats and !$quick )
		{
			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
		//-----------------------------------------
		
		if ( $quick )
		{
			return TRUE;
		}
		else
		{
			$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
	 * @return	boolean		Rebuild complete
	 */
	public function rebuildTopic( $tid )
	{
		/* Init */
		if ( ! $this->registry->isClassLoaded('topics') )
		{
			$classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'forums' ) . "/sources/classes/topics.php", 'app_forums_classes_topics', 'forums' );
			$this->registry->setClass( 'topics', new $classToLoad( $this->registry ) );
		}
		
		$this->registry->topics->rebuildTopic( $tid );
		
		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
	 * @deprecated
	 */
	public function topicAddReply( $post="", $tids=array(), $incpost=false )
	{
		require_once( IPSLib::getAppDir( 'forums' ) . '/sources/classes/post/classPost.php' );/*noLibHook*/
		$classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'forums' ) . '/sources/classes/post/classPostForms.php', 'classPostForms', 'forums' );
		
		$r = TRUE;
		foreach ( $tids as $row )
		{
			try
			{
				$_postClass  = new $classToLoad( $this->registry );
				$_postClass->setBypassPermissionCheck( true );
				$_postClass->setForumId( $row[1] );
				$_postClass->setTopicId( $row[0] );
				$_postClass->setAuthor( $this->memberData );
				$_postClass->setPostContent( $post );
				$_postClass->setSettings( array(
					'enableSignature'	=> TRUE,
					'enableEmoticons'	=> TRUE,
					) );
				$_postClass->setIncrementPostCountFlag( $incpost ? true : false );
				
				$_postClass->addReply();
			}
			catch ( Exception $e )
			{
				$r = FALSE;
			}
		}
		
		return $r;
	}
	
	/**
	 * 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 );
		
		/* GET AND EXAMINE */
		$topics = $this->registry->topics->getTopics( array( 'topicId' => $_ids, 'archiveState' => array( 'not', 'exclude' ), 'topicType' => array( 'all' ) ) );
		
		if ( ! count( $topics ) )
		{
			return false;
		}
		else
		{
			$ids = array_keys( $topics );
			$tid = " IN(" . implode( ",", $ids ) . ")";
		}
		
		/* 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 );
		
		/* Delete from recent posts */
		$this->registry->topics->deleteRecentPost( array( 'post_topic_id' => $ids ) );
		
		/* 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;
			}
		}
		
		/* GET AND EXAMINE */
		$topics = $this->registry->topics->getTopics( array( 'topicId' => $ids, 'archiveState' => array( 'not', 'exclude' ), 'topicType' => array( 'all' ) ) );
				
		if ( ! count( $topics ) )
		{
			return false;
		}
		else
		{
			$ids = array_keys( $topics );
			$tid = " IN(" . implode( ",", $ids ) . ")";
		}
		
		/* Remove from deletion log */
		IPSDeleteLog::removeEntries( $ids, 'topic', TRUE );
		
		/* Tagging */
		$this->registry->tags->deleteByMetaId( $ids );
		
		/* Delete from recent posts */
		$this->registry->topics->deleteRecentPost( array( 'post_topic_id' => $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 );
		
		if ( count( $posts ) )
		{
			$this->DB->delete( 'reputation_cache' , "app='forums' AND type='pid' AND type_id IN (" . implode( ',', $posts ) . ")");
			$this->DB->delete( 'reputation_index' , "app='forums' AND type='pid' AND type_id IN (" . implode( ',', $posts ) . ")" );
			$this->DB->delete( 'reputation_totals', "rt_key=MD5('forums;pid') AND rt_type_id IN (" . implode( ',', $posts ) . ")" );
		}
		
		//-----------------------------------------
		// 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' );
		}
		
		/* GET AND EXAMINE */
		$_topics = $this->registry->topics->getTopics( array( 'topicId' => $ids, 'archiveState' => array( 'not', 'exclude' ), 'topicType' => array( 'all' ) ) );
		
		if ( ! count( $_topics ) )
		{
			return false;
		}
		else
		{
			$ids = array_keys( $_topics );
			$tid = " IN(" . implode( ",", $ids ) . ")";
		}
		
		/* Update recent posts */
		$this->registry->topics->updateRecentPost( array( 'post_forum_id' => $moveto ), array( 'post_topic_id' => $ids ) );
		
		//-----------------------------------------
		// 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 );
			
		//-----------------------------------------
		// Update the voters
		//-----------------------------------------
		
		$this->DB->update( 'voters', 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() )
		{
			if ( ! $this->registry->getClass('topics')->isArchived( $row ) )
			{
				$_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' );
			
			/* Delete from recent posts */
			$this->registry->topics->restoreRecentPost( array( 'post_topic_id' => $_tids ) );
			
			/* Run moderation sync */
			$this->runModSync( 'topicUnhide', $_tids );
		}
		else
		{
			/* Delete from recent posts */
			$this->registry->topics->deleteRecentPost( array( 'post_topic_id' => $_tids ) );
			
			/* Run moderation sync */
			$this->runModSync( 'topicHide', $_tids );
		}
		
		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 ) ? 0 : 1 );
		
		$this->cache->rebuildCache( 'stats', 'global' );
		
		return TRUE;
	}

	/**
	 * Recount a forum
	 *
	 * @return	boolean
	 */
	public function forumRecount( $fid="" )
	{
		$fid = $fid ? intval($fid) : $this->forum['id'];
		
		$this->registry->class_forums->forumRebuild( $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 ( isset( $final_array['approved'] ) and $final_array['approved'] == -1 )
		{
			$this->_addToSoftDeleteLog( $id );
		}

		if ( is_array($id) )
		{
			$id = IPSLib::cleanIntArray( $id );
			
			if ( count($id) > 0 )
			{
				/* Ensure we don't moderate topics in archives */
				$this->DB->update( 'topics', $final_array, $this->registry->class_forums->fetchTopicArchiveQuery( array('not', 'exclude' ) ) . " AND tid IN(" . implode( ",", $id ) . ")" );
			}
			else
			{
				return false;
			}
		}
		else if ( intval($id) != "" )
		{
			$this->DB->update( 'topics', $final_array, $this->registry->class_forums->fetchTopicArchiveQuery( array('not', 'exclude' ) ) . " AND tid=" . intval($id) );
		}
		else
		{
			return false;
		}
		
		return true;
	}
	
	/**
	 * Add record to soft delete log
	 *
	 * @param	int|array	Topic ID(s)
	 */
	private function _addToSoftDeleteLog( $topicIds )
	{
		if ( !is_array( $topicIds ) )
		{
			$topicIds = array( $topicIds );
		}
		
		foreach ( $topicIds as $i )
		{
			$this->lang->loadLanguageFile( array( 'public_topic' ), 'forums' );
			$this->DB->replace( 'core_soft_delete_log', array(
				'sdl_obj_id'		=> $i,
				'sdl_obj_key'		=> 'topic',
				'sdl_obj_member_id'	=> $this->memberData['member_id'],
				'sdl_obj_date'		=> time(),
				'sdl_obj_reason'	=> $this->lang->words['mm_title'],
				'sdl_locked'		=> 0,
				), array( 'sdl_obj_id', 'sdl_obj_key' ) );
		}
	}
	
	/**
	 * 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' => -1 );

		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";
		}
		
		/* Archived */
		$sql .= ' AND ' . $this->registry->class_forums->fetchTopicArchiveQuery( array('not', 'exclude' ) );
		
		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 );
						}
					}
				}
			}
		}
	}
}