#!/usr/local/bin/php
<?php

/**
 * <pre>
 * Invision Power Services
 * IP.Board v3.4.6
 * Main public executable wrapper.
 * Set-up and load module to run
 * Last Updated: $Date: 2013-02-05 06:22:45 -0500 (Tue, 05 Feb 2013) $
 * </pre>
 *
 * @author 		$Author: mmecham $
 * @copyright	(c) 2001 - 2009 Invision Power Services, Inc.
 *
 * @package		IP.Board
 *
 * @version		$Rev: 11935 $
 *
 */

define( 'IPS_IS_SHELL', TRUE );
define( 'IPB_THIS_SCRIPT', 'public' );
define( 'IPS_CLI_MEMORY_DEBUG', false );

if ( is_file( './initdata.php' ) )
{
	require_once( './initdata.php' );/*noLibHook*/
}
else
{
	require_once( '../initdata.php' );/*noLibHook*/
}



require_once( IPS_ROOT_PATH . 'sources/base/ipsRegistry.php' );/*noLibHook*/
require_once( IPS_ROOT_PATH . 'sources/base/ipsController.php' );/*noLibHook*/

$reg = ipsRegistry::instance();
$reg->init();

$moo = new moo( $reg );

class moo
{
	protected $processed = 0;
	protected $parser;
	protected $oldparser;
	protected $start     = 0;
	protected $end       = 0;
	
	function __construct( ipsRegistry $registry )
	{
		$this->registry   =  $registry;
		$this->DB         =  $this->registry->DB();
		$this->settings   =& $this->registry->fetchSettings();
		$this->request    =& $this->registry->fetchRequest();
		$this->cache      =  $this->registry->cache();
		$this->caches     =& $this->registry->cache()->fetchCaches();
		$this->memberData = array();
		$this->stdin      =  fopen('php://stdin', 'r');
		
		//-----------------------------------------
		// Get new parser and old parser
		//-----------------------------------------

		require_once( IPS_ROOT_PATH . "sources/handlers/han_parse_bbcode.php" );/*noLibHook*/
		$this->parser		= new parseBbcode( $this->registry );
		$this->oldparser	= new parseBbcode( $this->registry, 'legacy' );
		
		$classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/text/parser.php', 'classes_text_parser' );
		$this->newParser    = new $classToLoad();
		
		//-----------------------------------------
		// Show options
		//-----------------------------------------
		
		$this->_print( "--------------------------------------------\nWelcome to the IP.Board Post Content Re-Builder\n--------------------------------------------\n" );
		$this->_print( "What do you wish to build?\n(c=Calendar Events, a=Announcements, x=Personal Conversations, s=Signatures, m=About me, p=posts, o=Remove Orphaned Posts)\nEnter: " );

		$option = $this->_fetchOption();

		switch( $option )
		{
			case 'c':
				$this->_doPosts('cal');
			break;
			case 'a':
				$this->_doPosts('announce');
			break;
			case 'x':
				$this->_doPosts('pms');
			break;
			case 's':
				$this->_doPosts('sigs');
			break;
			case 'm':
				$this->_doPosts('aboutme');
			break;
			case 'p':
				$this->_doPosts('posts');
			break;
			case 'o':
				$this->_doOrphanedPosts();
			break;
			default:
				$this->_doPosts('posts');
			break;
		}
	}
	
	/**
	 * Rebuild data
	 */
	protected function _doOrphanedPosts()
	{
		$batch  = 250;
		$c      = -1;
		$bucket = array();
		
		/* Lets grab the highest and lowest IDs */
		$rows = $this->DB->buildAndFetch( array( 'select'   => "count(pid) AS CNT, max(pid) AS MAX, min(pid) as MIN",
												 'from'     => array( 'posts' => 'p' ),
												 'where'    => 't.tid IS NULL',
												 'add_join' => array( array( 'select' => 't.*',
												 							 'from'   => array( 'topics' => 't' ),
												 							 'where'  => 't.tid=p.topic_id' ) ) ) );
		
		if ( empty( $rows['CNT'] ) )
		{
			$this->_print( "No orphaned posts have been found" );
			exit();
		}
		
		$this->DB->build( array( 'select' => 'pid, from_unixtime(post_date) as time',
								 'from'   => 'posts',
								 'where'  => 'pid IN( ' . $rows['MIN'] . ',' . $rows['MAX'] . ')' ) );
								 
		$this->DB->execute();
		
		while( $r = $this->DB->fetch() )
		{
			$posts[ $r['pid'] ] = $r['time'];
		}
		
		$this->_print( "--------------------------------------------\n" . $rows['CNT'] . " orphaned posts found\n--------------------------------------------\n" );
		$this->_print( "Oldest: PID:" . $rows['MIN'] . " - Date: " . $posts[ $rows['MIN'] ] );
		$this->_print( "Newest: PID:" . $rows['MAX'] . " - Date: " . $posts[ $rows['MAX'] ] );
		$this->_print( "Continue? (y/n):" );
		
		$option = $this->_fetchOption();
		
		if ( $option != 'y' )
		{
			$this->_print("Nothing deleted");
			exit();
		}
		
		$classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'forums' ) . '/sources/classes/moderate.php', 'moderatorLibrary', 'forums' );
		$this->modLibrary = new $classToLoad( $this->registry );
		
		/* Still here? */
		$this->DB->build( array( 'select'   => "pid",
								 'from'     => array( 'posts' => 'p' ),
								 'where'    => 't.tid IS NULL',
								 'add_join' => array( array( 'select' => 't.*',
								 							 'from'   => array( 'topics' => 't' ),
								 							 'where'  => 't.tid=p.topic_id' ) ) ) );
								 							 
		$o = $this->DB->execute();
		
		while( $post = $this->DB->fetch( $o ) )
		{
			$c++;
			
			$bucket[] = $post['pid'];
			
			if ( $c % $batch )
			{
				$this->modLibrary->postDeleteFromDb( $bucket, true, true );
				$this->_print( $c . ' deleted' );
				
				//$this->_print( var_export( $bucket, true ) );
				
				$bucket = array();
				$c  = 0;
			}
		}
		
		$this->_print( "All done" );
	}
	
	/**
	 * Rebuild data
	 */
	protected function _doPosts( $type )
	{
		//-----------------------------------------
		// Set up
		//-----------------------------------------
		
		$last	    = 0;
		$output     = array();
		$types	    = array( 'posts', 'pms', 'cal', 'announce', 'sigs', 'aboutme' );
		$type	    = in_array( $type, $types ) ? $type : 'posts';
		$groupCache = $this->caches['group_cache'];
		
		$this->_print( "Start at ID (Enter 0 to start from the very first entry)\nEnter: " );

		$this->start = intval( $this->_fetchOption() );
	
		$this->_print( "Process X rows (Enter 0 to process all rows)\nEnter: " );
	
		$this->end = intval( $this->_fetchOption() );
		
		//-----------------------------------------
		// Process batches...
		//-----------------------------------------
		
		while( ( $result = $this->_process( $type ) ) !== FALSE )
		{
			$this->_print( $result );
		}
	
		/* We're done.. */
		$this->_print( "COMPLETE" );
		
		/* Update isRebuilt cache */
		$sections		= array();
		$alreadyRebuilt	= $this->DB->buildAndFetch( array( 'select' => 'cs_value', 'from' => 'cache_store', 'where' => "cs_key='isRebuilt'" ) );

		if( $alreadyRebuilt['cs_value'] )
		{
			$sections	= explode( ',', $alreadyRebuilt['cs_value'] );
		}
		
		$sections[]	= $type;
		
		$this->DB->replace( 'cache_store', array( 'cs_key' => 'isRebuilt', 'cs_value' => implode( ',', $sections ) ), array( "cs_key" ) );
		
		/* Drop cache */
		if ( $type == 'posts' )
		{
			$this->DB->delete( 'content_cache_posts' );
		}
		else if ( $type == 'sigs' )
		{
			$this->DB->delete( 'content_cache_sigs' );
		}
	}
	
	/**
	 * Actually process
	 *
	 */
	protected function _process( $type )
	{
		if ( $this->end == 0 OR ( $this->end > $this->processed ) )
		{
			/* More to do... */
			$batch             = 250;
			$_s 	           = intval( $this->start );
			$done              = 0;
			$this->processed  += $batch;
			
			/* On mssql, the select query blocks all the updates.  Doing this causes the engine not to lock */
			if ( method_exists( $this->DB, 'setQueryRowLocking' ) )
			{
				$this->DB->setQueryRowLocking( 'OFF' );
			}
			
			switch( $type )
			{
				case 'cal':
					$this->DB->build( array( 'select' 	=> 'e.*',
															 'from' 	=> array( 'cal_events' => 'e' ),
															 'order' 	=> 'e.event_id ASC',
															 'where'    => 'e.event_id > ' . $_s,
															 'limit' 	=> array( $batch ),
															 'add_join'	=> array( 	1 => array( 'type'		=> 'left',
															  									'select'	=> 'm.member_group_id, m.mgroup_others',
															  								  	'from'		=> array( 'members' => 'm' ),
															  								  	'where' 	=> "m.member_id=e.event_member_id"
															  						)	)
													) 		);
				break;

				case 'announce':
					$this->DB->build( array( 'select' 	=> 'a.*',
															 'from' 	=> array( 'announcements' => 'a' ),
															 'order' 	=> 'a.announce_id ASC',
															 'where'    => 'a.announce_id > ' . $_s,
															 'limit' 	=> array( $batch ),
															 'add_join'	=> array( 	1 => array( 'type'		=> 'left',
															  									'select'	=> 'm.member_group_id, m.mgroup_others',
															  								  	'from'		=> array( 'members' => 'm' ),
															  								  	'where' 	=> "m.member_id=a.announce_member_id"
															  						)	)
													) 		);
				break;

				case 'pms':
					$this->DB->build( array( 'select' 	=> 'p.*',
															 'from' 	=> array( 'message_posts' => 'p' ),
															 'order' 	=> 'p.msg_id ASC',
															 'where'    => 'p.msg_id > ' . $_s,
															 'limit' 	=> array( $batch ),
															 'add_join'	=> array( 	1 => array( 'type'		=> 'left',
															  									'select'	=> 'm.member_group_id, m.mgroup_others',
															  								  	'from'		=> array( 'members' => 'm' ),
															  								  	'where' 	=> "m.member_id=p.msg_author_id"
															  						)	)
													) 		);
				break;

				case 'sigs':
					$this->DB->build( array( 'select' 	=> 'me.signature, me.pp_member_id',
															 'from' 	=> array( 'profile_portal' => 'me' ),
															 'order' 	=> 'me.pp_member_id ASC',
															 'where'	=> "me.signature IS NOT NULL AND me.signature != '' AND me.pp_member_id > " . $_s,
															 'limit' 	=> array( $batch ),
															 'add_join'	=> array( 	1 => array( 'type'		=> 'left',
															  									'select'	=> 'm.member_group_id, m.mgroup_others, m.members_display_name',
															  								  	'from'		=> array( 'members' => 'm' ),
															  								  	'where' 	=> "m.member_id=me.pp_member_id"
															  						)	)
													) 		);
				break;

				case 'aboutme':
					$this->DB->build( array( 'select' 	=> 'pp.pp_about_me, pp.pp_member_id',
															 'from' 	=> array( 'profile_portal' => 'pp' ),
															 'order' 	=> 'pp.pp_member_id ASC',
															 'where'	=> "pp.pp_about_me != '' AND pp.pp_about_me IS NOT NULL AND pp.pp_member_id > " . $_s,
															 'limit' 	=> array( $batch ),
															 'add_join'	=> array( 	1 => array( 'type'		=> 'left',
															  									'select'	=> 'm.member_group_id, m.mgroup_others, m.members_display_name',
															  								  	'from'		=> array( 'members' => 'm' ),
															  								  	'where' 	=> "m.member_id=pp.pp_member_id"
															  						)	)
													) 		);
				break;

				case 'posts':
					$this->DB->build( array( 'select' 	=> 'p.*',
															 'from' 	=> array( 'posts' => 'p' ),
															 'order' 	=> 'p.pid ASC',
															 'where'    => 'p.pid > ' . $_s,
															 'limit' 	=> array( $batch ),
															 'add_join'	=> array( 	1 => array( 'type'		=> 'left',
															 									'select'	=> 't.forum_id',
															  								  	'from'		=> array( 'topics' => 't' ),
															  								  	'where' 	=> "t.tid=p.topic_id"
															  						),
															  						2 => array( 'type'		=> 'left',
															  									'select'	=> 'm.member_group_id, m.mgroup_others',
															  								  	'from'		=> array( 'members' => 'm' ),
															  								  	'where' 	=> "m.member_id=p.author_id"
															  						),
																					3 => array( 'type'		=> 'left',
															  									'select'	=> 'f.*',
															  								  	'from'		=> array( 'forums' => 'f' ),
															  								  	'where' 	=> "t.forum_id=f.id"
															  						) ) ) );
				break;
			}

			$outer = $this->DB->execute();

			//-----------------------------------------
			// Process...
			//-----------------------------------------

			while( $r = $this->DB->fetch( $outer ) )
			{
				//-----------------------------------------
				// Reset
				//-----------------------------------------
		
				$this->parser->quote_open				= $this->oldparser->quote_open			    = 0;
				$this->parser->quote_closed			    = $this->oldparser->quote_closed			= 0;
				$this->parser->quote_error			    = $this->oldparser->quote_error			    = 0;
				$this->parser->error					= $this->oldparser->error					= '';
				$this->parser->image_count			    = $this->oldparser->image_count			    = 0;
				$this->parser->parsing_mgroup			= $this->oldparser->parsing_mgroup		    = $r['member_group_id'];
				$this->parser->parsing_mgroup_others	= $this->oldparser->parsing_mgroup_others	= $r['mgroup_others'];
			
				/* Clear cached queries */
				$this->DB->obj['cached_queries'] = array();
			
				$this->memberData['g_bypass_badwords'] = $this->caches['group_cache'][ $r['member_group_id'] ]['g_bypass_badwords'];

				switch( $type )
				{
					case 'cal':
						$this->parser->parse_smilies	= $this->oldparser->parse_smilies	= $r['event_smilies'];
						$this->parser->parse_html		= $this->oldparser->parse_html	= 0;
						$this->parser->parse_bbcode	= $this->oldparser->parse_bbcode	= 1;
						$this->parser->parsing_section		= 'calendar';

						$rawpost = $this->oldparser->preEditParse( $r['event_content'] );
					break;

					case 'announce':
						$this->parser->parse_smilies	= $this->oldparser->parse_smilies	= 1;
						$this->parser->parse_html		= $this->oldparser->parse_html	= $r['announce_html_enabled'];
						$this->parser->parse_bbcode	= $this->oldparser->parse_bbcode	= 1;
						$this->parser->parse_nl2br	= $this->oldparser->parse_nl2br	= $r['announce_nlbr_enabled'];
						$this->parser->parsing_section		= 'announcement';

						$rawpost = $this->oldparser->preEditParse( $r['announce_post'] );
					break;

					case 'pms':
						$this->parser->parse_smilies	= $this->oldparser->parse_smilies	= 1;
						$this->parser->parse_html		= $this->oldparser->parse_html	= $this->caches['group_cache'][ $r['member_group_id'] ]['g_dohtml'];
						$this->parser->parse_bbcode	    = $this->oldparser->parse_bbcode	= 1;
						$this->parser->parse_nl2br	    = $this->oldparser->parse_nl2br	= 1;
						$this->parser->parsing_section	= 'pms';

						$rawpost = $this->oldparser->preEditParse( $r['msg_post'] );
					break;

					case 'sigs':
						$this->parser->parse_smilies	= $this->oldparser->parse_smilies	= 0;
						$this->parser->parse_html		= $this->oldparser->parse_html	= $this->caches['group_cache'][ $r['member_group_id'] ]['g_dohtml'];
						$this->parser->parse_bbcode	= $this->oldparser->parse_bbcode	= 1;
						$this->parser->parse_nl2br	= $this->oldparser->parse_nl2br	= 1;
						$this->parser->parsing_section		= 'signatures';

						$rawpost = $this->oldparser->preEditParse( $r['signature'] );
					break;

					case 'aboutme':
						$this->parser->parse_smilies	= $this->oldparser->parse_smilies	= 1;
						$this->parser->parse_html		= $this->oldparser->parse_html	= $this->caches['group_cache'][ $r['member_group_id'] ]['g_dohtml'];
						$this->parser->parse_bbcode	= $this->oldparser->parse_bbcode	= 1;
						$this->parser->parse_nl2br	= $this->oldparser->parse_nl2br	= 1;
						$this->parser->parsing_section		= 'aboutme';

						$rawpost = $this->oldparser->preEditParse( $r['pp_about_me'] );
					break;

					case 'posts':
						$allowHtml = ( $r['post_htmlstate'] && $r['use_html'] && $this->caches['group_cache'][ $r['member_group_id'] ]['g_dohtml'] );
						
						$this->newParser->set( array( 'parseArea'      => 'topics',
													  'memberData'     => $r,
													  'parseBBCode'    => $r['use_ibc'],
													  'parseHtml'      => $allowHtml,
													  'parseEmoticons' => $r['use_emo'] ) );

						$rawpost = $this->newParser->BBCodeToHtml( $r['post'] );
					break;
				}
				
				/* @link http://community.---.com/tracker/issue-36434-rebuild-post-content-remove-break-from-post-content/ New parser retains <br>, legacy retains \n */
				if ( $type == 'posts' || $type == 'sigs' )
				{
					/* Anything special here? */
					$newpost = $rawpost;
				}
				else
				{
					if ( strstr( $rawpost, "\n" ) )
					{
						$rawpost = nl2br( $rawpost );
					}
				
					$newpost = $this->parser->preDbParse( $rawpost );
				}
				
				//-----------------------------------------
				// Remove old \' escaping
				//-----------------------------------------

				$newpost = str_replace( "\\'", "'", $newpost );

				//-----------------------------------------
				// Convert old dohtml?
				//-----------------------------------------

				$htmlstate = 0;

				if ( strstr( strtolower($newpost), '[dohtml]' ) )
				{
					//-----------------------------------------
					// Can we use HTML?
					//-----------------------------------------

					if ( $type == 'posts' AND $this->registry->class_forums->forum_by_id[ $r['forum_id'] ]['use_html'] )
					{
						$htmlstate = 2;
					}

					$newpost = preg_replace( "#\[dohtml\]#i" , "", $newpost );
					$newpost = preg_replace( "#\[/dohtml\]#i", "", $newpost );
				}
				else
				{
					$htmlstate = intval( $r['post_htmlstate'] );
				}

				//-----------------------------------------
				// Convert old attachment tags
				//-----------------------------------------

				$newpost = preg_replace( "#\[attachmentid=(\d+?)\]#is", "[attachment=\\1:attachment]", $newpost );

				if ( $newpost OR $type == 'sigs' OR $type == 'aboutme' )
				{
					switch( $type )
					{
						case 'posts':
							$this->DB->update( 'posts', array( 'post' => $newpost, 'post_htmlstate' => $htmlstate ), 'pid='.$r['pid'] );
							$string = substr( $r['post'], 0, 30 ) . '...';
							$last = $r['pid'];
						break;

						case 'pms':
							$this->DB->update( 'message_posts', array( 'msg_post' => $newpost ), 'msg_id='.$r['msg_id'] );
							$string = substr( $r['msg_post'], 0, 30 ) . '...';
							$last = $r['msg_id'];
						break;

						case 'sigs':
							$this->DB->update( 'profile_portal', array( 'signature' => $newpost ), 'pp_member_id='.$r['pp_member_id'] );
							$string = substr( $r['members_display_name'], 0, 30 ) . '...';
							$last = $r['pp_member_id'];
						break;

						case 'aboutme':
							$this->DB->update( 'profile_portal', array( 'pp_about_me' => $newpost ), 'pp_member_id='.$r['pp_member_id'] );
							$string = substr( $r['post'], 0, 30 ) . '...';
							$last = $r['pp_member_id'];
						break;

						case 'cal':
							$this->DB->update( 'cal_events', array( 'event_content' => $newpost ), 'event_id='.$r['event_id'] );
							$string = substr( $r['event_content'], 0, 30 ) . '...';
							$last = $r['event_id'];
						break;

						case 'announce':
							$this->DB->update( 'announcements', array( 'announce_post' => $newpost ), 'announce_id='.$r['announce_id'] );
							$string = substr( $r['announce_post'], 0, 30 ) . '...';
							$last = $r['announce_id'];
						break;
					}
				}
				
				unset( $r );
				$done++;
			}
			
			if ( method_exists( $this->DB, 'setQueryRowLocking' ) )
			{
				$this->DB->setQueryRowLocking( 'ON' );
			}
		}
		
		/* Attempt to free memory */
		$this->DB->freeResult( $outer );
		
		/* Memory Debug */
		if ( IPS_CLI_MEMORY_DEBUG )
		{
			IPSDebug::setMemoryDebugFlag( "Done", 0 );
		
			foreach( IPSDebug::$memory_debug  as $i )
			{
				$this->_print( "Debug:... " . $i[0] . ' - ' . IPSLib::sizeFormat( $i[1] ) );
			}
		
			/* Reset */
			IPSDebug::$memory_debug = array();
		}
		
		if ( $done )
		{
			$this->start = intval( $last );
			return "Completed... " . $this->processed . "(last ID[ " . $last . " ] " . str_replace( "\n", "", $string ) . ")";
		}
		else
		{
			return FALSE;
		}
	}
	
	
	/**
	 * Out to stdout
	 */
	protected function _print( $message, $newline="\n" )
	{
		$stdout = fopen('php://stdout', 'w');
		fwrite( $stdout, $message . $newline );
		fclose( $stdout );
	}
	
	/* Fetch option
	 *
	 */
	protected function _fetchOption()
	{
		return trim( fgets( $this->stdin ) );
	}
}

exit();                 



?>