<?php

    if (!defined( "PLOG_CLASS_PATH" )) {
        define( "PLOG_CLASS_PATH", dirname(__FILE__)."/");
    }

    set_time_limit (5 * 3600);

    //
    // enable this for debugging purposes
    //
    define( "DB_WIZARD_DEBUG", false );

    //
    // in case you're having problems with time outs while upgrading (probably too
    // many records) lower this figure
    //
    define( "WIZARD_MAX_RECORDS_PER_STEP", 75 );
    
    //
    // minimum php version required
    //
    define( "MIN_PHP_VERSION", "4.2.0" );
    
    //
    // whether data transformers should fail on error by default
    // It might be convenient to set this to 'false' if we're running
    // the wizard on top of an already updated installation
    //
    define( "DATABASE_DATA_TRANSFORMER_FAIL_ON_ERROR_DEFAULT", true );

    // many hosts don't have this enabled and we, for the time being, need it...
    ini_set("arg_seperator.output", "&amp;");

    include_once( PLOG_CLASS_PATH."class/controller/controller.class.php" );
    include_once( PLOG_CLASS_PATH."class/template/templateservice.class.php" );
    include_once( PLOG_CLASS_PATH."class/action/action.class.php" );
    include_once( PLOG_CLASS_PATH."class/database/db.class.php" );
    include_once( PLOG_CLASS_PATH."class/template/template.class.php" );
    include_once( PLOG_CLASS_PATH."class/view/view.class.php" );
    include_once( PLOG_CLASS_PATH."class/data/validator/stringvalidator.class.php" );
    include_once( PLOG_CLASS_PATH."class/data/validator/integervalidator.class.php" );
    include_once( PLOG_CLASS_PATH."class/data/validator/emailvalidator.class.php" );
    include_once( PLOG_CLASS_PATH."class/data/validator/passwordvalidator.class.php" );
    include_once( PLOG_CLASS_PATH."class/data/timestamp.class.php" );    
    include_once( PLOG_CLASS_PATH."class/net/http/httpvars.class.php" );
    include_once( PLOG_CLASS_PATH."class/misc/version.class.php" );
    include_once( PLOG_CLASS_PATH."class/file/file.class.php" );
    include_once( PLOG_CLASS_PATH."class/file/finder/filefinder.class.php" );
    include_once( PLOG_CLASS_PATH."class/gallery/resizers/gddetector.class.php" );
    include_once( PLOG_CLASS_PATH."class/config/configfilestorage.class.php" );
    include_once( PLOG_CLASS_PATH."class/data/textfilter.class.php" );
    include_once( PLOG_CLASS_PATH."class/locale/locales.class.php" );
    include_once( PLOG_CLASS_PATH."class/locale/localefinder.class.php" );
    include_once( PLOG_CLASS_PATH."class/template/templatesets/templatesets.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/bloginfo.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/users.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/blogs.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/articlecategories.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/articles.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/mylinkscategories.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/userpermissions.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/blogcategories.class.php" );
    include_once( PLOG_CLASS_PATH."class/dao/globalarticlecategories.class.php" );
    include_once( PLOG_CLASS_PATH."class/gallery/dao/galleryalbums.class.php" );     
    
    // table schemas
    include_once( PLOG_CLASS_PATH."install/dbschemas.properties.php" );
    // default configuration values for 1.1
    include_once( PLOG_CLASS_PATH."install/defaultconfig.properties.php" );
    // post schema changes after upgrade
    include_once( PLOG_CLASS_PATH."install/changes_10_11.properties.php" );

    define( "TEMP_FOLDER", "./tmp" );

    // maps used to map requests with actions
    $_actionMap["Checks"] = "WizardChecks";
    $_actionMap["Default"] = "WizardChecks";
    $_actionMap["Intro"] = "WizardIntro";
    $_actionMap["Step1"] = "WizardStepOne";
    $_actionMap["Step2"] = "WizardStepTwo";
    $_actionMap["Step3"] = "WizardStepThree";
    $_actionMap["Step4"] = "WizardStepFour";
    $_actionMap["Step5"] = "WizardStepFive";
    $_actionMap["Update1"] = "UpdateStepOne";
    $_actionMap["Update2"] = "UpdateStepTwo";
    $_actionMap["Update3"] = "UpdateStepThree";


    /**
     * Open a connection to the database
     */
     function connectDb( $ignoreError = false , $selectDatabase = true )
     {
        $config = new ConfigFileStorage();
        // open a connection to the database
        //$db = NewADOConnection('mysql');
        $db = PDb::getDriver('mysql');
        
        if ( $selectDatabase ) {
            $res = $db->Connect($config->getValue( "db_host" ), $config->getValue( "db_username" ), $config->getValue( "db_password" ), $config->getValue( "db_database" ), $config->getValue( "db_character_set" ));
        } else {
            $res = $db->Connect($config->getValue( "db_host" ), $config->getValue( "db_username" ), $config->getValue( "db_password" ), null, $config->getValue( "db_character_set" ));
        }

        if( DB_WIZARD_DEBUG )
            $db->debug = true;

        // return error
        if( $ignoreError )
            return $db;

        if( !$res )
            return false;

        return $db;
    }

    /**
     * Returns the database prefix
     */
    function getDbPrefix()
    {
        $config = new ConfigFileStorage();
        return $config->getValue( "db_prefix" );
    }

    /**
     * some useful little functions
     */
    class WizardTools
    {
       /**
        * returns true if plog has already been installed before or
        * false otherwise
        */
       function isNewInstallation()
       {
           $configFile = new ConfigFileStorage();
           // if plog hasn't been installed, this file will have empty settings
           if( $configFile->getValue( "db_host") == "" && $configFile->getValue( "db_username") == "" &&
               $configFile->getValue( "db_database") == "" && $configFile->getValue( "db_prefix" ) == "" &&
               $configFile->getValue( "db_password" ) == "" )
               $isNew = true;
           else
               $isNew = false;

           return( $isNew );
       }
    }

    /**
     * Renders a template file.
     */
    class WizardView extends View
    {

        var $_templateName;

        function WizardView( $templateName )
        {
            $this->View();
            $this->_templateName = $templateName;
        }

        function render()
        {
            // build the file name
            $templateFileName = "wizard/".$this->_templateName.".template";

            //$t = new Template( $templateFileName, "" );
            $t = new Smarty();
            $v = new Version();
            $this->_params->setValue( "version", $v->getVersion());
            $this->_params->setValue( "projectPage", $v->getProjectPage());
            $this->_params->setValue( "safeMode", ini_get("safe_mode"));
            $t->assign( $this->_params->getAsArray());
            $t->template_dir    = "./templates";
            $t->compile_dir     = TEMP_FOLDER;
            $t->cache_dir       = TEMP_FOLDER;
            $t->use_sub_dirs    = false;
            $t->caching = false;

            print $t->fetch( $templateFileName );
        }
    }
    
    class WizardAction extends Action
    {
        function WizardAction( $actionInfo, $request )
        {
            $this->Action( $actionInfo, $request );
        }
    }    
    
    class WizardValidator
    {
        var $_desc;
        var $_critical;
        var $_valid;
        var $_solution;
    
        function WizardValidator( $desc = "", $solution = "", $critical = true )
        {            
            $this->_desc = $desc;
            $this->_critical = $critical;
            $this->_valid = false;
            $this->_solution = $solution;
        }
        
        function isCritical()
        {
            return( $this->_critical );
        }
        
        function getDesc()
        {
            return( $this->_desc );
        }
        
        function isValid()
        {
            return( $this->_valid );
        }
        
        function getSolution()
        {
            return( $this->_solution );
        }
        
        function validate() 
        {
            return( $this->_valid );
        }        
    }
    
    class WizardPhpVersionValidator extends WizardValidator
    {
        function WizardPhpVersionValidator( $minVersion = MIN_PHP_VERSION )
        {
            $this->WizardValidator( "Checking if the installed <b>PHP</b> version is at least $minVersion", 
                                    "Please upgrade your version of PHP to $minVersion or newer",
                                    true );
            $this->_minVersion = $minVersion;
        }
    
        function validate()
        {
            $this->_valid = version_compare( phpversion(), $this->_minVersion ) >= 0;
            return( parent::validate());
        }
    }
    
    class WizardWritableFileValidator extends WizardValidator
    {
        var $_file;
        
        function WizardWritableFileValidator( $file )
        {
            $this->WizardValidator( "Checking if file/folder <b>$file</b> is writable", 
                                    "Please make sure that the file is writable by the web server",
                                    true );
            $this->_file = $file;
        }
    
        function validate()
        {
            $this->_valid = File::isWritable( $this->_file );
            return( parent::validate());
        }
    }
    
    class WizardSessionFunctionsAvailableValidator extends WizardValidator
    {
        function WizardSessionFunctionsAvailableValidator()
        {
            $this->WizardValidator( "Checking if <b>session</b> functions are available", 
                                    "LifeType requires support for sessions to be part of your PHP installation",
                                    true );
        }
    
        function validate()
        {
            $this->_valid = function_exists( "session_start" ) &&
                            function_exists( "session_destroy" ) &&
                            function_exists( "session_cache_limiter" ) &&
                            function_exists( "session_name" ) &&
                            function_exists( "session_set_cookie_params" ) &&
                            function_exists( "session_save_path" );
            return( parent::validate());            
        }
    }

    class WizardSessionSettingsValidator extends WizardValidator
    {
        function WizardSessionSettingsValidator()
        {
            $this->WizardValidator( "Checking if <b>session.auto_start</b> is disabled", 
                                    "LifeType can only run when session.auto_start is disabled.",           
                                    true );
        }    
    
        function validate()
        {
            $this->_valid = (ini_get( "session.auto_start" ) == "0");
            return( parent::validate());    
        }
    }
       
    class WizardMySQLFunctionsAvailableValidator extends WizardValidator
    {
        function WizardMySQLFunctionsAvailableValidator()
        {
            $this->WizardValidator( "Checking if <b>MySQL</b> functions are available", 
                                    "LifeType requires support for MySQL to be part of your PHP installation",
                                    true );
        }
    
        function validate()
        {
            $this->_valid = function_exists( "mysql_select_db" ) &&
                            function_exists( "mysql_query" ) &&
                            function_exists( "mysql_connect" ) &&
                            function_exists( "mysql_fetch_assoc" ) &&
                            function_exists( "mysql_num_rows" ) &&
                            function_exists( "mysql_free_result" );
            return( parent::validate());                            
        }        
    }
    
    class WizardXmlFunctionsAvailableValidator extends WizardValidator
    {
        function WizardXmlFunctionsAvailableValidator()
        {
            $this->WizardValidator( "Checking if <b>XML</b> functions are available", 
                                    "LifeType requires support for XML to be part of your PHP installation",
                                    true );
        }    
    
        function validate()
        {
            $this->_valid = function_exists( "xml_set_object" ) &&
                            function_exists( "xml_set_element_handler" ) &&
                            function_exists( "xml_parser_create" ) &&
                            function_exists( "xml_parser_set_option" ) &&
                            function_exists( "xml_parse" ) &&
                            function_exists( "xml_parser_free" );
            return( parent::validate());                            
        }        
    }
    
    class WizardSafeModeValidator extends WizardValidator
    {
        function WizardSafeModeValidator()
        {
            $this->WizardValidator( "Checking if <b>safe mode</b> is enabled", 
                                    "LifeType can run when PHP's safe mode is enabled, but please see <a href=\"<span style=\"color:red;font-weight:bold\">ERROR</span>the project wiki</a> for more details",           
                                    false );
        }    
    
        function validate()
        {
            $this->_valid = (ini_get( "safe_mode" ) == "");
            return( parent::validate());    
        }
    }

    class WizardIconvFunctionsAvailableValidator extends WizardValidator
    {
        function WizardIconvFunctionsAvailableValidator()
        {
            $this->WizardValidator( "Checking if <b>iconv</b> functions are available", 
                                    "LifeType requires support for some resource metadata conversion and some LifeType plugins requires support for multi-byte language encoding/decoding.",
                                    false );
        }
    
        function validate()
        {
            $this->_valid = function_exists( "iconv" );
            return( parent::validate());                            
        }        
    }

    class WizardMbstringFunctionsAvailableValidator extends WizardValidator
    {
        function WizardMbstringFunctionsAvailableValidator()
        {
            $this->WizardValidator( "Checking if <b>mbstring</b> functions are available", 
                                    "Some LifeType plugins requires support for multi-byte language encoding/decoding.",
                                    false );
        }
    
        function validate()
        {
            $this->_valid = function_exists( "mb_convert_encoding" );
            return( parent::validate());                            
        }        
    }

    class WizardGdFunctionsAvailableValidator extends WizardValidator
    {
        function WizardGdFunctionsAvailableValidator()
        {
            $this->WizardValidator( "Checking if <b>gd</b> or <b>gd2</b> functions are available", 
                                    "LifeType requires support for generating image thumbnail.",
                                    false );
        }
    
        function validate()
        {
            $this->_valid = function_exists( "imagecopyresampled" ) &&
            				function_exists( "imagecopyresized" );
            return( parent::validate());                            
        }        
    }
    
    class WizardFileUploadsValidator extends WizardValidator
    {
        function WizardFileUploadsValidator()
        {
            $this->WizardValidator( "Checking if <b>file_uploads</b> is enabled", 
                                    "LifeType requires support for uploading resources.",
                                    true );
        }    
    
        function validate()
        {
            $this->_valid = (ini_get( "file_uploads" ) == 1);
            return( parent::validate());    
        }
    }
    
    class WizardCtypeFunctionsAvailableValidator extends WizardValidator
    {
        function WizardCtypeFunctionsAvailableValidator()
        {
            $this->WizardValidator( "Checking if <b>ctype</b> functions are available", 
                                    "Some LifeType plugins requires support for variable type validation.",
                                    false );
        }
    
        function validate()
        {
            $this->_valid = function_exists( "ctype_digit" );
            return( parent::validate());                            
        }        
    }               
    
    class WizardChecks extends WizardAction
    {        
        function perform()
        {
            // build the array with checks
            $checkGroups['File permissions checking'] = Array(
               "writeConfigFile" => new WizardWritableFileValidator( "config/config.properties.php" ),
               "writeTmpFolder" => new WizardWritableFileValidator( "tmp" ),
               "writeGalleryFolder" => new WizardWritableFileValidator( "gallery" )
            );
            
            $checkGroups['PHP version checking'] = Array(
               "php" => new WizardPhpVersionValidator()
            );

            $checkGroups['PHP configuration checking'] = Array(
               "sessionSettings" => new WizardSessionSettingsValidator(),
               "safemode" => new WizardSafeModeValidator(),
               "fileUploads" => new WizardFileUploadsValidator()
            );
            
            $checkGroups['PHP functions availability checking'] = Array(
               "sessions" => new WizardSessionFunctionsAvailableValidator(),
               "mysql" => new WizardMySQLFunctionsAvailableValidator(),
               "xml" => new WizardXmlFunctionsAvailableValidator(),
               "iconv" => new WizardIconvFunctionsAvailableValidator(),
               "mbstring" => new WizardMbstringFunctionsAvailableValidator(),
               "gd" => new WizardGdFunctionsAvailableValidator(),
               "ctype" => new WizardCtypeFunctionsAvailableValidator()
            );                                    
            
            // run the checks
            $ok = true;
            foreach( $checkGroups as $checkGroup => $checks ) {
	            foreach( $checks as $id => $check ) {
	                $valid = $checkGroups[$checkGroup][$id]->validate();
	                // if it doesn't validate but it's not critical, then we can proced too
	                if( !$checkGroups[$checkGroup][$id]->isCritical())
	                    $valid = true;  
	                $ok = ($ok && $valid);
	            }
	        }
            
            // create the view and pass the results
            $this->_view = new WizardView( "checks" );
            $this->_view->setValue( "ok", $ok );
            $this->_view->setValue( "checkGroups", $checkGroups );
            
            if( WizardTools::isNewInstallation())
                $this->_view->setValue( "mode", "install" );
            else
                $this->_view->setValue( "mode", "update" );

            return true;
        }
    }
    
    class WizardPagedAction extends WizardAction
    {
        var $willRefresh;
    
        function WizardPagedAction( $actionInfo, $request ) 
        {
            $this->WizardAction( $actionInfo, $request );
            
            $this->willRefresh = false;
        }
        
        /**
         * @private
         */
        function getPageFromRequest()
        {
            include_once( PLOG_CLASS_PATH."class/data/validator/integervalidator.class.php");    	
			// get the value from the request
			$page = HttpVars::getRequestValue( "page" );
			// but first of all, validate it
			$val = new IntegerValidator();
			if( !$val->validate( $page ))
				$page = 1;			
							
			return $page;        
        }
        
        /**
         * @private
         */
        function willRefresh()
        {
            return( $this->willRefresh );
        }
    }

    /**
     * Gets the information about the database from the user.
     */
    class WizardIntro extends WizardAction
    {
        function WizardIntro( $actionInfo, $request )
        {
            $this->WizardAction( $actionInfo, $request );
        }

        function perform()
        {
            // we can detect whether plog is already installed or not and direct users to the right
            // place
            if( WizardTools::isNewInstallation())
                $this->_view = new WizardView( "intro" );
            else {
                Controller::setForwardAction( "Update1" );
                return false;
            }

            $this->setCommonData();
            return true;
        }
    }

    /**
     *
     * Saves data to the configuration file
     *
     */
    class WizardStepOne extends WizardAction
    {

        var $_dbServer;
        var $_dbUser;
        var $_dbPassword;
        var $_dbName;
        var $_dbPrefix;
        var $_connection;

        function WizardStepOne( $actionInfo, $request )
        {
            $this->WizardAction( $actionInfo, $request );

            // data validation
            $this->registerFieldValidator( "dbServer", new StringValidator());
            $this->registerFieldValidator( "dbUser", new StringValidator());
            $this->registerFieldValidator( "dbPassword",  new StringValidator(), true );
            $this->registerFieldValidator( "dbName", new StringValidator());
            $this->registerFieldValidator( "dbPrefix", new StringValidator(), true );
            $errorView = new WizardView( "intro" );
            $errorView->setErrorMessage( "Some data was incorrect or missing." );
            $this->setValidationErrorView( $errorView );
        }

        function perform()
        {
            // fetch the data needed from the request
            $this->_dbServer   = $this->_request->getValue( "dbServer" );
            $this->_dbUser     = $this->_request->getValue( "dbUser" );
            $this->_dbPassword = $this->_request->getValue( "dbPassword" );
            $this->_dbName     = $this->_request->getValue( "dbName" );
            $this->_skipThis   = $this->_request->getValue( "skipDbInfo" );
            $this->_dbPrefix   = $this->_request->getValue( "dbPrefix", DEFAULT_DB_PREFIX );

            // we should now save the data to the configuration file, just before
            // we read it
            $configFile = new ConfigFileStorage();

            // we expect everything to be fine
            $errors = false;

            // before doing anything, we should check of the configuration file is
            // writable by this script, or else, throw an error and bail out gracefully
            $configFileName = $configFile->getConfigFileName();
            if( !File::exists( $configFileName )) {
                if (! File::touch( $configFileName ) ) {
                    $this->_view = new WizardView( "intro" );
                    $message = "Could not create the LifeType configuration file $configFileName. Please make sure
                                that the file can be created by the user running the webserver. It is needed to
                                store the database configuration settings.";
                    $this->_view->setErrorMessage( $message );
                    $this->setCommonData( true );
                    return false;
                } else {
                    ConfigFileStorage::createConfigFile( $configFileName );
                }
            }
            if( File::exists( $configFileName ) && !File::isWritable( $configFileName )) {
                $this->_view = new WizardView( "intro" );
                $message = "Please make sure that the file $configFileName can be written by this script during
                            the installation process. It is needed to store the database configuration settings. Once the
                            installation is complete, please revert the permissions to no writing possible.";
                $this->_view->setErrorMessage( $message );
                $this->setCommonData( true );
                return false;
            }

            // continue if everything went fine
            if( !$configFile->saveValue( "db_username", $this->_dbUser ) ||
                !$configFile->saveValue( "db_password", $this->_dbPassword ) ||
                !$configFile->saveValue( "db_host", $this->_dbServer ) ||
                !$configFile->saveValue( "db_database", $this->_dbName ) ||
                !$configFile->saveValue( "db_prefix", $this->_dbPrefix )) {
                $errors = true;
            }

            if( $errors ) {
                $message = "Could not save values to the configuration file. Please make sure it is available and
                            that has write permissions for the user under your web server is running.";
                $this->_view = new WizardView( "intro" );
                $this->_view->setErrorMessage( $message );

                return( false );
            }
            else {
                $connectionEsablished = false;

                $this->_connection = @mysql_connect( $this->_dbServer, $this->_dbUser, $this->_dbPassword );
                if( $this->_connection ) {
                    $connectionEsablished = true;
                } else {
                    $connectionEsablished = false;
                    $message = "There was an error connecting to the database. Please check your settings.";
                }

                if ( !$connectionEsablished ) {
                    $this->_view = new WizardView( "step1" );
                    $this->_view->setErrorMessage( $message );
                    $this->setCommonData( true );
                    return false;
                } else {
                	$this->_view = new WizardView( "step1" );
	                $availableCharacterSets = $this->getAvailableCharacterSets();
	                $defaultCharacterSet = $this->getDatabaseCharacterSet();
	                $createDatabase = false;
	                if( empty( $defaultCharacterSet ) )
	                {
	                	$defaultCharacterSet = $this->getServerCharacterSet();
	                	$createDatabase = true;
	                }
	                $this->_view->setValue( "availableCharacterSets", $availableCharacterSets );
	                $this->_view->setValue( "defaultCharacterSet", $defaultCharacterSet );
	                $this->_view->setValue( "createDatabase", $createDatabase );
	                // now we better read the information from the config file to make sure that
	                // it has been correctly saved
	                $this->setCommonData( true );
	                return true;
	            }
            }
        }

	    function getAvailableCharacterSets()
	    {
	        // check mysql version first. Version lower than 4.1 doesn't support utf8
	        $serverVersion = mysql_get_server_info( $this->_connection );
	        $version = explode( '.', $serverVersion );
	        if ( $version[0] < 4 ) return false;
	        if ( ( $version[0] == 4 ) && ( $version[1] == 0 ) ) return false;
	        
	        // check if utf8 support was compiled in
	        $result = mysql_query( "SHOW CHARACTER SET", $this->_connection );
	        if( $result )
	        {
		        if( mysql_num_rows($result) > 0 ) {
		            // iterate through resultset
		            $availableCharacterSets = array();
		            while( $row = mysql_fetch_array( $result, MYSQL_ASSOC ) )
		            {
						array_push( $availableCharacterSets, $row['Charset'] );
		            }
		            return $availableCharacterSets;
		        }
		    }
	        return false;
	    }
	    
	    function getDatabaseCharacterSet()
	    {
			if( !@mysql_select_db( $this->_dbName, $this->_connection ) ) {
				return false;
			}
	        
	        // We use a SHOW CREATE DATABASE command to show the original
	        // SQL character set when DB was created.
	        $result = mysql_query( "SHOW CREATE DATABASE `".$this->_dbName."`", $this->_connection );
	        if( $result )
	        {
		        if( mysql_num_rows( $result ) < 0 ) {
		            // The specified db name is wrong!
		            return false;
		        }
		        $dbInfo = mysql_fetch_row( $result );
		        $pattern = '/40100 DEFAULT CHARACTER SET (\w+) /';
		        if( ( preg_match( $pattern, $dbInfo[1], $match ) > 0 ) ) {
		            return $match[1];
		        }
		    }
	        return false;
	    }
	
	    function getServerCharacterSet(){
	        // We use a SHOW CREATE DATABASE command to show the original
	        // SQL character set when DB was created.
	        $result = mysql_query( "SHOW VARIABLES LIKE 'character_set_server'", $this->_connection );
	        if( $result )
	        {
		        if( mysql_num_rows( $result ) > 0 ) {
		            $row = mysql_fetch_array( $result, MYSQL_ASSOC );
		            return $row['Value'];
		        }
			}
	        return false;
	    }
    }

    /**
     *
     * Second step where we connect to the database and create the tables.
     *
     */
    class WizardStepTwo extends WizardAction
    {

        var $_db;
        var $_database;
        var $_dbCharacterSet;
        var $_createDatabase;

        function setDbConfigValues( &$view )
        {
            $configFile = new ConfigFileStorage();
            $configFile->reload();
            $view->setValue( "dbUser", $configFile->getValue( "db_username" ));
            $view->setValue( "dbPassword", $configFile->getValue( "db_password" ));
            $view->setValue( "dbServer", $configFile->getValue( "db_host" ));
            $view->setValue( "dbName", $configFile->getValue( "db_database" ));
            $view->setValue( "dbPrefix", $configFile->getValue( "db_prefix" ));
            $view->setValue( "dbCharacterSet", $configFile->getValue( "db_character_set" ));
            return true;
        }

        function perform()
        {
            global $Tables;
            global $Inserts;

			$this->_dbCharacterSet = $this->_request->getValue( "dbCharacterSet" );
            $configFile = new ConfigFileStorage();
			$configFileName = $configFile->getConfigFileName();
            
            if( File::exists( $configFileName ) && !File::isWritable( $configFileName )) {
                $this->_view = new WizardView( "step1" );
                $message = "Please make sure that the file $configFileName can be written by this script during
                            the installation process. It is needed to store the database configuration settings. Once the
                            installation is complete, please revert the permissions to no writing possible.";
                $this->_view->setErrorMessage( $message );
                $this->setCommonData( true );
                return false;
            }

            // continue if everything went fine
            if( !$configFile->saveValue( "db_character_set", $this->_dbCharacterSet ) ) {
                $message = "Could not save values to the configuration file. Please make sure it is available and
                            that has write permissions for the user under your web server is running.";
                $this->_view = new WizardView( "step1" );
                $this->_view->setErrorMessage( $message );

                return false;
            }
            
            $createDb = $this->_request->getValue( "createDatabase" );
            $message = '';

            // only check for errors in case the database table should already exist!
            if( !$createDb ) {
                $connectionEsablished = false;

                // Lets check the 'everything is fine' case first..
                $this->_db = connectDb();
                if( $this->_db ) {
                     $connectionEsablished = true;
                } else {
                     $connectionEsablished = false;
                     $message = "There was an error selecting the database. Please verify the database was already created or check the 'Create database' checkbox.";
                }

                // We were unable to connect to the db and select the right db.. lets try
                // just to connect.. maybe the database needs to be created (even though the
                // user did not check the appropriate box).
                if ( !$connectionEsablished ) {
                    $this->_db = connectDb( true, false );
                    if( !$this->_db ) {
                         $message = "There was an error connecting to the database. Please check your settings.";
                    }
                }

                if ( !$connectionEsablished ) {
                    $this->_view = new WizardView( "step1" );
                    $this->setDbConfigValues( $this->_view );
                    $this->_view->setErrorMessage( $message );
                    $this->setCommonData( true );
                    return false;
                }
            }

            $config = new ConfigFileStorage();
            $this->_database = $config->getValue( "db_database" );
            $this->_dbPrefix = $config->getValue( "db_prefix" );

            // create the database
            if( $createDb ) {
                $this->_db = connectDb( false, false );
                if( !$this->_db->Execute( "CREATE DATABASE ".$this->_database )) {
                    $message = "Error creating the database: ".$this->_db->ErrorMsg();
                    $this->_view = new WizardView( "step1" );
                    $this->setDbConfigValues( $this->_view );
                    $this->_view->setErrorMessage( $message );
                    $this->setCommonData( true );
                    return false;
                } else {
                    $message = "Database created successfully.<br/>";
                }
            }

            // reconnect using the new database.
            $config = new ConfigFileStorage();
            $this->_db->Connect( $config->getValue( "db_host" ), 
                                 $config->getValue( "db_username" ), 
                                 $config->getValue( "db_password" ), 
                                 $config->getValue( "db_database" ));
                                 
            // create a data dictionary to give us the right sql code needed to create the tables
            $dict = NewPDbDataDictionary( $this->_db );

           // create the tables
            $errors = false;            
            
            foreach( $Tables as $name => $table ) {
            	$upperName = $dict->upperName;
            	$tableSchema = $table["schema"];
            	if ( isset( $table["options"] ) )
            	{
            		$tableOptions = $table["options"];
            		$options = array ( $upperName => $tableOptions );
            	} else {
            		$options = array ();
                }
                $sqlarray = $dict->CreateTableSQL( $this->_dbPrefix.$name, $tableSchema, $options );

                // each table may need more than one sql query because of indexes, triggers, etc...
                $ok = true;
                foreach( $sqlarray as $sql ) {
                    $ok = ( $ok && $this->_db->Execute( $sql ));
                }
                
                if( $ok )
                    $message .= "Table <strong>$name</strong> created successfully.<br/>";
                else {
                    $message .= "Error creating table $name: ".$this->_db->ErrorMsg()."<br/>";
                    $errors = true;
                }                    
            }

            if( $errors ) {
                $message = "There was an error creating the tables in the database. Please make sure that the user chosen to connect to the database has enough permissions to create tables.<br/><br/>$message";
                $this->_view = new WizardView( "step1" );
                $this->_view->setErrorMessage( $message );
                $this->setDbConfigValues( $this->_view );
                $this->setCommonData();
                return false;
            }

             // try to guess the url where plog is running
             $httpProtocol = (array_key_exists("HTTPS", $_SERVER) && $_SERVER["HTTPS"] == "on") ? "https://" : "http://";
             $httpHost = $_SERVER["HTTP_HOST"];
             $requestUrl = $_SERVER["REQUEST_URI"];
             $requestUrl = str_replace( "/wizard.php", "", $requestUrl );
             $plogUrl = $httpProtocol.$httpHost.$requestUrl;

            // Find some of the tools we are going to need (last one is for os x, with fink installed)
            // TBD: support for Windows specific directories
            $folders = Array( "/bin/", "/usr/bin/", "/usr/local/bin/", "/sw/bin/" );
            $finder = new FileFinder();
            $pathToUnzip = $finder->findBinary( "unzip", $folders );
            $pathToTar = $finder->findBinary( "tar", $folders);
            $pathToGzip = $finder->findBinary( "gzip", $folders);
            $pathToBzip2 = $finder->findBinary( "bzip2", $folders);
            $pathToConvert = $finder->findBinary( "convert", $folders);

            // and execute some insert's
            foreach( $Inserts as $insert ) {
                $query = str_replace( "{dbprefix}", $this->_dbPrefix, $insert );
                $query = str_replace( "{plog_base_url}", $plogUrl, $query );
                // replace also the placeholders for the paths to the tools
                $query = str_replace( "{path_to_tar}", $pathToTar, $query );
                $query = str_replace( "{path_to_unzip}", $pathToUnzip, $query );
                $query = str_replace( "{path_to_bz2}", $pathToBzip2, $query );
                $query = str_replace( "{path_to_gzip}", $pathToGzip, $query );
                $query = str_replace( "{path_to_convert}", $pathToConvert, $query );
                $query = str_replace( "{path_to_convert}", $pathToConvert, $query );
                if( !$this->_db->Execute( $query )) {
                    $message .= "Error executing code: ".$this->_db->ErrorMsg()."<br/>";
                    $errors = true;
                }
            }

            //
            // show some information regarding the helper tools we're going to need
            // and wether they were found or not
            //
            $message .= "<br/><b>-- Helper tools --</b><br/>";
            if( $pathToTar == "" )
                $message .= "The helper tool 'tar' was not found<br/>";
            else
                $message .= "The helper tool 'tar' was found in $pathToTar<br/>";
            if( $pathToGzip == "" )
                $message .= "The helper tool 'gzip' was not found<br/>";
            else
                $message .= "The helper tool 'gzip' was found in $pathToGzip<br/>";
            if( $pathToUnzip == "" )
                $message .= "The helper tool 'unzip' was not found<br/>";
            else
                $message .= "The helper tool 'unzip' was found in $pathToUnzip<br/>";
            if( $pathToBzip2 == "" )
                $message .= "The helper tool 'bzip2' was not found<br/>";
            else
                $message .= "The helper tool 'bzip2' was found in $pathToTar<br/>";
            if( $pathToConvert == "" )
                $message .= "The helper tool 'convert' (from the ImageMagick package) was not found<br/>";
            else
                $message .= "The helper tool 'convert' (from the ImageMagick package) was found in $pathToConvert<br/>";

            if( $errors ) {
                $this->_view = new WizardView( "step1" );
                $this->setDbConfigValues( $this->_view );
                $message = "There was an error initializing some of the tables. Please make sure that the user chosen to connect to the database has enough permissions to add records to the database.<br/><br/>$message";
                $this->_view->setErrorMessage( $message );
                $this->setCommonData();
            }
            else {
                $this->_view = new WizardView( "step2" );
                $this->_view->setValue( "message", $message );
            }

            // Scan for locales
            $locales = new Locales();
            // find all the new locales that we have not yet stored
            $f = new LocaleFinder();
            $newLocaleCodes = $f->find();

            foreach( $newLocaleCodes as $newLocaleCode ) {
                $res = $locales->addLocale( $newLocaleCode );
            }

            return true;
        }
    }

    /**
     *
     * this action only shows some feedback
     *
     */
    class WizardStepThree extends WizardAction
    {
        function perform()
        {
            $this->_view = new WizardView( "step3" );
            $this->setCommonData();
        }
    }

    /**
     *
     * Create the first user in the database
     *
     */
    class WizardStepFour extends WizardAction
    {

        var $_userName;
        var $_userPassword;
        var $_confirmPassword;
        var $_userEmail;
        var $_userFullName;

        function WizardStepFour( $actionInfo, $request )
        {
            $this->WizardAction( $actionInfo, $request );

            $this->registerFieldValidator( "userName", new StringValidator());
            $this->registerFieldValidator( "userPassword", new PasswordValidator());
            $this->registerFieldValidator( "userPasswordCheck", new PasswordValidator());
            $this->registerFieldValidator( "userEmail", new EmailValidator());
            $this->registerField( "userFullName" );
            $view = new WizardView( "step3" );
            $view->setErrorMessage( "Some data is missing or incorrect" );
            $this->setValidationErrorView( $view );
        }

        // creates the user
        function perform()
        {
            $this->_userName = $this->_request->getValue( "userName" );
            $this->_userPassword = $this->_request->getValue( "userPassword" );
            $this->_confirmPassword = $this->_request->getValue( "userPasswordCheck" );
            $this->_userEmail = $this->_request->getValue( "userEmail" );
            $this->_userFullName = $this->_request->getValue( "userFullName" );

            $db = connectDb();

            if( !$db ) {
                $this->_view = new WizardView( "step3" );
                $this->_view->setErrorMessage( "There was an error connecting to the database. Please check your settings." );
                $this->setCommonData();
                return false;
            }

            if( $this->_confirmPassword != $this->_userPassword ) {
                $this->_view = new WizardView( "step3" );
                $this->_form->setFieldValidationStatus( "userPasswordCheck", false );
                $this->setCommonData( true );
                return false;
            }

            $dbPrefix = Db::getPrefix();

            $users = new Users();
            $user = new UserInfo( $this->_userName,
                                  $this->_userPassword,
                                  $this->_userEmail,
                                  "",
                                  $this->_userFullName);
			// set the user as an administrator
			$user->setSiteAdmin( true );
			// and add this record to the db
            $userId = $users->addUser( $user );
            if( !$userId ) {
                $this->_view = new WizardView( "step3" );
                $message = "There was an error adding the user. Make sure that the user does not already exist in the database (".$users->DbError().")";
                $this->_view->setErrorMessage( $message );
                $this->setCommonData();
                return false;
            }

            // we also have to execute the code to give administrator privileges to this user
            $query = "INSERT INTO {$dbPrefix}users_permissions(user_id,blog_id,permission_id) VALUES( $userId, 0, 1 );";
            $db->Execute( $query );

            $this->_view = new Wizardview( "step4" );
            $this->_view->setValue( "ownerid", $userId );
            $this->_view->setValue( "siteLocales", Locales::getLocales());
            $this->_view->setValue( "defaultLocale", Locales::getDefaultLocale());
            $ts = new TemplateSets();
            $this->_view->setValue( "siteTemplates", $ts->getGlobalTemplateSets());
            $this->setCommonData();
            return true;
        }

    }

    class WizardStepFive extends WizardAction
    {

        var $_blogName;
        var $_ownerId;
        var $_blogProperties;

        function WizardStepFive( $actionInfo, $request )
        {
              $this->WizardAction( $actionInfo, $request );

              $this->registerFieldValidator( "blogName", new StringValidator());
              $this->registerFieldValidator( "ownerid", new IntegerValidator());
              $this->registerFieldValidator( "blogTemplate", new StringValidator());
              $this->registerFieldValidator( "blogLocale", new StringValidator());			  
              $view = new WizardView( "step4" );
              $view->setErrorMessage( "Some data is missing or incorrect" );
              $view->setValue( "siteLocales", Locales::getLocales());
			  $view->setValue( "defaultLocale", Locales::getDefaultLocale());
              $ts = new TemplateSets();
              $view->setValue( "siteTemplates", $ts->getGlobalTemplateSets());
              $this->setValidationErrorView( $view );
        }

        function perform()
        {
            // Before we add a new blog, we need to add blog category and global article category first
            // add blog category
            $blogCategories = new BlogCategories();
            $blogCategory = new BlogCategory( "General", "General" );
            $blogCategoryId = $blogCategories->addBlogCategory( $blogCategory );

            // add global article category
            $globalArticleCategories = new GlobalArticleCategories();
            $globalArticleCategory = new GlobalArticleCategory( "General", "General" );
            $globalArticleCategoryId = $globalArticleCategories->addGlobalArticleCategory( $globalArticleCategory );
            
            // retrieve the values from the view
            $this->_blogName = $this->_request->getValue( "blogName" );
            $this->_ownerId  = $this->_request->getValue( "ownerid" );
            $this->_blogProperties = $this->_request->getValue( "properties" );
            $this->_blogTemplate = $this->_request->getValue( "blogTemplate" );
            $this->_blogLocale = $this->_request->getValue( "blogLocale" );

            // configure the blog
            $blogs = new Blogs();
            $blog = new BlogInfo( $this->_blogName, $this->_ownerId, "", "" );
            // set the default BlogCategory id to blog
            $blog->setBlogCategoryId( $blogCategoryId );
            $blog->setProperties( $this->_blogProperties );
            $blog->setStatus( BLOG_STATUS_ACTIVE );
            $blogSettings = $blog->getSettings();
            $blogSettings->setValue( "locale", $this->_blogLocale );
            $blogSettings->setValue( "template", $this->_blogTemplate );
            $blog->setSettings( $blogSettings );

            // and now save it to the database
            $newblogId = $blogs->addBlog( $blog );
            if( !$newblogId ) {
                $this->_view = new WizardView( "step4" );
                $this->_view->setValue( "siteLocales", Locales::getLocales());
                $ts = new TemplateSets();
                $this->_view->setValue( "siteTemplates", $ts->getGlobalTemplateSets());
                $this->_view->setErrorMessage( "There was an error creating the new blog" );
                $this->setCommonData( true );
                return false;
            }

            // if the blog was created, we can add some basic information
            // add a category
            $articleCategories = new ArticleCategories();
            $articleCategory = new ArticleCategory( "General", "General", $newblogId, true );
            $catId = $articleCategories->addArticleCategory( $articleCategory );

            // load the right locale
            $locale =& Locales::getLocale( $this->_blogLocale );
            // and load the right text
            $articleTopic = $locale->tr( "register_default_article_topic" );
            $articleText  = $locale->tr( "register_default_article_text" );
            $article = new Article( $articleTopic, $articleText, Array( $catId ), $this->_ownerId, $newblogId, POST_STATUS_PUBLISHED, 0, Array(), "welcome" );
            // set the default ArticleGlobalCategory id to article
            $article->setGlobalCategoryId( $globalArticleCategoryId );
            // set the current time to article
            $t = new Timestamp();
            $article->setDateObject( $t );
            $articles = new Articles();
            $articles->addArticle( $article );

            // add a new first album so that users can start uploading stuff right away
            $t = new Timestamp();
            $album = new GalleryAlbum( $newblogId,
                                       "General",
                                       "General",
                                       GALLERY_RESOURCE_PREVIEW_AVAILABLE,
                                       0,
                                       $t->getTimestamp(),
                                       Array(),
                                       true );
            $albums = new GalleryAlbums();
            $albums->addAlbum( $album );
            
            // add a new default mylinkscategory
            $linksCategory = new MyLinksCategory( "General", $newblogId );
            $linksCategories = new MyLinksCategories();
            $linksCategories->addMyLinksCategory( $linksCategory );         

            // save a few things in the default configuration
            $config =& Config::getConfig();
            // default blog id
            $config->saveValue( "default_blog_id", (int)$newblogId );
            // default locale
            $config->saveValue( "default_locale", $this->_blogLocale );
            // and finally, the default template
            $config->saveValue( "default_template", $this->_blogTemplate );

            //
            // detect wether we have GD available and set the blog to use it
            //
            if( GdDetector::detectGd()) {
                $config->saveValue( "thumbnail_method", "gd" );
                $message = "GD has been detected and set as the backend for dealing with images.";
            }
            else {
                $pathToConvert = $config->getValue( "path_to_convert" );
                if( $pathToConvert ) {
                    $config->saveValue( "thumbnail_method", "imagemagick" );
                    $message = "ImageMagick has been detected and set as the backend for dealing with images.";
                }
                else {
                    // nothing was found, so we'll have to do away with the 'null' resizer...
                    $config->saveValue( "thumbnail_method", "null" );
                    $message = "Neither GD nor ImageMagick have been detected in this host so it will not be possible to generate thumbnails from images.";
                }
            }
            
            // clean the data cache to avoid problems when we're done
            include_once( PLOG_CLASS_PATH."class/cache/cachemanager.class.php" );
            $cache =& CacheManager::getCache();
            $cache->clearCache();

            $this->_view = new WizardView( "step5" );
            $this->_view->setValue( "message", $message );
            return true;
        }
    }

    class UpdateStepOne extends WizardAction
    {

        function perform()
        {
            $this->_view = new WizardView( "update1" );
            WizardStepTwo::setDbConfigValues( $this->_view );
            $this->setCommonData();
        }
    }

    class UpdateStepTwo extends WizardAction
    {

        var $_db;
        var $_dbPrefix;

        function validate()
        {
            $configFile = new ConfigFileStorage();
            $this->_dbPrefix = $configFile->getValue( "db_prefix" );

            return true;
        }

        
        function perform()
        {
            global $Tables;
            global $Inserts;

            // connect to the db
            $this->_db = connectDb();

            // store error messages in this var
            $message = "";

            if( !$this->_db ) {
                $this->_view = new WizardView( "update1" );
                WizardStepTwo::setDbConfigValues( $this->_view );
                $this->_view->setErrorMessage( "There was an error connecting to the database. Please check your settings." );
                return false;
            }

            // ---
            // make changes to the tables that need changes, but leave it up to the data dictionary
            // to take care of the changes
            // ---
            $dict = NewPDbDataDictionary( $this->_db );            
            $errors = false;
            foreach( $Tables as $name => $table ) {
                $errorMessage = "";
                $table_errors = false;
            	$upperName = $dict->upperName;
            	$tableSchema = $table["schema"];
            	if ( isset( $table["options"] ) )
            	{
            		$tableOptions = $table["options"];
            		$options = array ( $upperName => $tableOptions );
            	} else {
            		$options = array ();
                }
               
                // generate the code with the changes for the table
                $sqlarray = $dict->ChangeTableSQL( $this->_dbPrefix.$name, $tableSchema, $options );
                
                foreach( $sqlarray as $sql ) {
                    // and run the query
                    if( !$this->_db->Execute( $sql )) {
                        $table_errors = true;
                        $errors = true;
                        $errorMessage .= $this->_db->ErrorMsg()."<br/>";
                    }
                }

                if( !$table_errors )
                    $message .= "Changes to table <strong>$name</strong> executed successfully.<br/>";
                else {
                    $message .= "Error modifying table $name: ".$errorMessage;
                }
            }

            if( !$errors ) {
                $message .= "<br/><b>** Modifications to the database schema carried out successfully **</b><br/><br/>";
                $message .= "The next step will update some of the data in your database. This process may take a while
                             depending on the amount of data in your database, and the browser will periodically refresh
                             to avoid timeout issues. <b>Please do not attempt to interrupt this process.</b>";
                             
            }

            // check the configuration and add the new configuration settings that were added for 1.1
            foreach( $Inserts as $key => $insert ) {
				$checkKeyQuery = "SELECT * FROM ".$this->_dbPrefix."config WHERE config_key ='".$key."';";
				$result = $this->_db->Execute($checkKeyQuery);
                if(!$result){
                    $message .= "Error executing code: ".$this->_db->ErrorMsg()."<br/>";
                    $errors = true;
                }
				else{
                    if ($result->RecordCount() == 0){
                            // try to guess the url where plog is running
                        $httpProtocol = (array_key_exists("HTTPS", $_SERVER) && $_SERVER["HTTPS"] == "on") ? "https://" : "http://";
                        $httpHost = $_SERVER["HTTP_HOST"];
                        $requestUrl = $_SERVER["REQUEST_URI"];
                        $requestUrl = str_replace( "/wizard.php", "", $requestUrl );
                        $plogUrl = $httpProtocol.$httpHost.$requestUrl;
                            // Find some of the tools we are going to need (last one is for os x, with fink installed)
                            // TBD: support for Windows specific directories
                        $folders = Array( "/bin/", "/usr/bin/", "/usr/local/bin/", "/sw/bin/" );
                        $finder = new FileFinder();
                        $pathToUnzip = $finder->findBinary( "unzip", $folders );
                        $pathToTar = $finder->findBinary( "tar", $folders);
                        $pathToGzip = $finder->findBinary( "gzip", $folders);
                        $pathToBzip2 = $finder->findBinary( "bzip2", $folders);
                        $pathToConvert = $finder->findBinary( "convert", $folders);
                            // replace the de prefix and base url
                        $query = str_replace( "{dbprefix}", $this->_dbPrefix, $insert );
                        $query = str_replace( "{plog_base_url}", $plogUrl, $query );
                            // replace also the placeholders for the paths to the tools
                        $query = str_replace( "{path_to_tar}", $pathToTar, $query );
                        $query = str_replace( "{path_to_unzip}", $pathToUnzip, $query );
                        $query = str_replace( "{path_to_bz2}", $pathToBzip2, $query );
                        $query = str_replace( "{path_to_gzip}", $pathToGzip, $query );
                        $query = str_replace( "{path_to_convert}", $pathToConvert, $query );
                        $query = str_replace( "{path_to_convert}", $pathToConvert, $query );
                        if( !$this->_db->Execute( $query )) {
                            $message .= "Error executing code: ".$this->_db->ErrorMsg()."<br/>";
                            $errors = true;
                        }
                    }
                    $result->Close();
                }
            }
                // check to see if we need to remove duplicates and the id index
            $query = "SELECT id FROM ".$this->_dbPrefix."config LIMIT 1";
            $result = $this->_db->Execute($query);
                // if $result is false, id column has already been removed
            if($result){
                $result->Close();
            
                    // remove all duplicates in plog_config table
                    
                    // first create temp table without the duplicates
                $query = "CREATE TEMPORARY TABLE tmptable ".
                         "SELECT * FROM ".$this->_dbPrefix."config WHERE 1 GROUP BY config_key";
                $result = $this->_db->Execute($query);
                if($result){
                    $result->Close();
                        // Now delete the old table
                    $query = "DELETE FROM ".$this->_dbPrefix."config";
                    $result = $this->_db->Execute($query);
                    if($result){
                        $result->Close();
                        // Insert the unique rows into the old table
                        $query = "INSERT INTO ".$this->_dbPrefix."config SELECT * FROM tmptable";
                        $result = $this->_db->Execute($query);
                    }
                }
                if(!$result){
                    $message .= "Error removing duplicates in config table: ".
                        $this->_db->ErrorMsg()."<br/>";
                    $errors = true;
                }

                if($result){

                    // remove index id field, we don't need it any more!
                    $query = "ALTER TABLE ".$this->_dbPrefix."config DROP COLUMN id";
                    $result = $this->_db->Execute($query);
                    if(!$result){
                        $message .= "Error removing old id column from config table: ".$this->_db->ErrorMsg()."<br/>";
                        $errors = true;
                    }
                }
            }
            
            //
            // there's nothing left to do so we can quit now!!
            //
            if( !$errors ) {
                $this->_view = new WizardView( "update2" );
                $this->_view->setValue( "message", $message );
            }
            else {
                $this->_view = new WizardView( "update1" );
                WizardStepTwo::setDbConfigValues( $this->_view );
                $this->_view->setErrorMessage( $message );
            }

            return true;
        }
    }
    
    /**
     * Generic class that performs data updates on the database
     */
    class DatabaseDataTransformer extends Model
    {
        /**
         * @public
         * Public fields, may be accessed by other classes
         */
        var $updatedRecords;
        var $message;
        var $errorRecords;
        var $notModifiedRecords;
        var $failOnError;
        var $page;
        var $itemsPerPage;
        var $dbPrefix;
    
        function DatabaseDataTransformer( $page = -1, $itemsPerPage = WIZARD_MAX_RECORDS_PER_STEP )
        {
            $this->Model();
            
            $this->updatedRecords = 0;
            $this->errorRecords   = 0;
            $this->notModifiedRecords = 0;
            $this->addedRecords = 0;
            $this->deletedRecords = 0;
            $this->failOnError = DATABASE_DATA_TRANSFORMER_FAIL_ON_ERROR_DEFAULT;
            $this->message = "";
            
            $this->page = $page;
            $this->itemsPerPage = $itemsPerPage;
            
            $this->dbPrefix = $this->getPrefix();
        }
        
        /**
         * Rerforms the transformation. Returns true if the step was successful or false otherwise.
         * Upon finalization, please check the $message string to get more information. Use the $updatedRecords,
         * $errorRecords, $notModifiedRecords, $addedRecords and $deletedRecords for some figures regarding the 
         * previous step
         */
        function perform()
        {
            // must be implemented by child classes
            return true;        
        }
        
        /**
         * Returns true if there is no more data for this transformer to upgrade, or false otherwise
         *
         * @return True if ready or false if not
         */
        function isComplete()
        {
            return( $this->getNumSteps() <= $this->page );
        }
        
        /**
         * returns the number of steps needed to process this data
         */
        function getNumSteps( $table = "" )
        {
            // if there is a table name, we can take a shortcut or else we expect child
            // classes to reimplement this method
            if( $table ) {
                $numItems = $this->getNumItems( $this->getPrefix().$table );
                
                $numSteps = ceil( $numItems / $this->itemsPerPage );
            }
            else {
                $numSteps = 0;
            }
            
            return( $numSteps );
        }
        
        /**
         * returns the total number of records processed so far based on the current page and the
         * number of items per page
         */
        function getTotalProcessedRecords()
        {
            return( $this->page * $this->itemsPerPage );
        }
        
        /**
         * returns an approximate percentage of records processed so far
         */
        function getPercentProcessed()
        {
            $processed = $this->getTotalProcessedRecords();
            return((int)($processed / ( $this->getNumSteps() * $this->itemsPerPage ) * 100 ));
        }
    }
    
    /**
     * updates the article category counters based on the number of posts that have
     * been assigned to each category
     */
    class CategoryCountersDataTransformer extends DatabaseDataTransformer
    {        
        function getNumSteps()
        {
            return( parent::getNumSteps( "articles_categories" ));
        }
    
        function perform()
        {
            $this->message = "<b>Updating article categories (step 2 of 9)</b><br/><br/>";        
        
            // load each one of the categories and update them
            // list of categories
            $query3 = "SELECT id FROM ".$this->dbPrefix."articles_categories";            
            $res3 = $this->Execute( $query3, $this->page, $this->itemsPerPage );
            $catIds = Array();
            if( $res3->RecordCount() == 0 ) {
				$this->message .= "No more records to process";
				return( true );
			}
            while( $row = $res3->FetchRow()) {
            	$catIds[] = $row["id"];
            }
            $where = "(".implode( $catIds, "," ).")";
            
            // total number of articles
            $query1 = "SELECT category_id, COUNT(*) AS total FROM ".$this->dbPrefix."article_categories_link ".
                      "WHERE category_id IN {$where} GROUP BY category_id";
            // number of active articles
            $query2 = "SELECT l.category_id AS category_id, COUNT(*) AS total FROM ".
                       $this->dbPrefix."article_categories_link l, ".$this->dbPrefix."articles a ".
                       "WHERE a.id = l.article_id AND a.status = ".POST_STATUS_PUBLISHED." AND l.category_id IN {$where} ".
                       "GROUP BY l.category_id";                   
                      
            // execute the 1st query
            $res1 = $this->Execute( $query1 );
            if( !$res1 ) {
                $this->message .= "Error performing changes to the categories table";
                return false;
            }
            $numArticles = Array();
            while( $row = $res1->FetchRow()) {
                $numArticles[$row["category_id"]] = $row["total"];
            }
            $res1->Close();
                        
            // total number of active comments
            $res2 = $this->Execute( $query2 );
            if( !$res2 ) {
                $this->message .= "Error performing changes to the categories table";
                return false;
            }
            $numActiveArticles = Array();
            while( $row = $res2->FetchRow()) {
                $numActiveArticles[$row["category_id"]] = $row["total"];
            }
            $res2->Close();                       
            
			foreach( $catIds as $catId ) {
                // load the counters
                $totalArticles = '';
                $totalActiveArticles = '';
                if( isset( $numArticles[$catId] ))
                    $totalArticles = $numArticles[$catId];                    
                if( $totalArticles == '' ) $totalArticles = 0;
                if( isset( $numActiveArticles[$catId] ))
                    $totalActiveArticles = $numActiveArticles[$catId];
                if( $totalActiveArticles == '' ) $totalActiveArticles = 0;
                
                // build the update query
                $query = "UPDATE ".$this->dbPrefix."articles_categories SET num_articles = {$totalArticles},
                          num_published_articles = {$totalActiveArticles},
                          last_modification = last_modification
                          WHERE id = {$catId}";
                          
                // and execute it
                $result = $this->Execute( $query );
                if( !$result ) {
                    $this->message .= "Error updating category with id {$catId}<br/>";
                }
                else
                    $this->updatedRecords++;
            }
            $res3->Close();
        
            $this->message .= "{$this->updatedRecords} categories updated, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";
            return true;        
        }
    }
    
    /**
     * updates blog article counters
     */
    class ArticleCountersDataTransformer extends DatabaseDataTransformer
    {        
        function getNumSteps()
        {
            return( parent::getNumSteps( "articles" ));
        }
        
        function perform()
        {
            include_once( PLOG_CLASS_PATH."class/dao/articles.class.php" );
            include_once( PLOG_CLASS_PATH."class/dao/articlecommentstatus.class.php" );
            
            $this->message = "<b>Updating articles (step 3 of 9)</b><br/><br/>";           
                        
            // article ids
            $artIds = Array();            
            $query4 = "SELECT id FROM ".$this->dbPrefix."articles";            
            $res4 = $this->Execute( $query4, $this->page, $this->itemsPerPage );
            if( $res4->RecordCount() == 0 ) {
				$this->message .= "No more records to process";				
				return( true );
            }
            while( $row = $res4->FetchRow()) {
            	$artIds[] = $row["id"];
            }
            $where = "(".implode( $artIds, "," ).")";
            
            // build the queries
            $query1 = "SELECT article_id, COUNT(*) AS total FROM ".$this->dbPrefix."articles_comments ".
                      "WHERE article_id IN {$where} GROUP BY article_id";
            $query2 = "SELECT article_id, COUNT(*) AS total FROM ".$this->dbPrefix."articles_comments ".
                      "WHERE article_id IN {$where} AND status = ".COMMENT_STATUS_NONSPAM." GROUP BY article_id";
            $query3 = "SELECT article_id, COUNT(*) AS total FROM ".$this->dbPrefix."trackbacks ".
                      "WHERE article_id IN {$where} GROUP BY article_id";
            
            // and execute all of them...
            
            // total number of comments
            $res1 = $this->Execute( $query1 );
            if( !$res1 ) {
                $this->message .= "Error performing changes to the articles table";
                return false;
            }
            $numComments = Array();
            while( $row = $res1->FetchRow()) {
                $numComments[$row["article_id"]] = $row["total"];
            }
            $res1->Close();
            
            // total number of active comments
            $res2 = $this->Execute( $query2 );
            if( !$res2 ) {
                $this->message .= "Error performing changes to the articles table";
                return false;
            }
            $numActiveComments = Array();
            while( $row = $res2->FetchRow()) {
                $numActiveComments[$row["article_id"]] = $row["total"];
            }
            $res2->Close();
            
            // number of trackbacks
            $res3 = $this->Execute( $query3 );
            if( !$res3 ) {
                $this->message .= "Error performing changes to the articles table";
                return false;
            }
            $numTrackbacks = Array();            
            while( $row = $res3->FetchRow()) {
                $numTrackbacks[$row["article_id"]] = $row["total"];
            }           
            $res3->Close();            
            
            foreach( $artIds as $artId ) {
                // load the counters
                if(isset($numComments[$artId]))
                    $totalComments = $numComments[$artId];
                else
                    $totalComments = 0;

                if(isset($numActiveComments[$artId]))
                    $totalActiveComments = $numActiveComments[$artId];
                else
                    $totalActiveComments = 0;

                if(isset($numTrackbacks[$artId]))
                    $totalTrackbacks = $numTrackbacks[$artId];
                else
                    $totalTrackbacks = 0;
                
                // build the update query
                $query = "UPDATE ".$this->dbPrefix."articles SET num_comments = {$totalComments},
                          num_nonspam_comments = {$totalActiveComments},
                          num_trackbacks = {$totalTrackbacks},
                          num_nonspam_trackbacks = {$totalTrackbacks},
                          date = date,
                          modification_date = modification_date
                          WHERE id = {$artId}";
                          
                // and execute it
                $result = $this->Execute( $query );
                if( !$result ) {
                    $this->message .= "Error updating article with id {$artId}<br/>";
                }
                else
                    $this->updatedRecords++;
            }
            $res4->Close();
            
            $processed = $this->page * $this->itemsPerPage;            
            $this->message .= "{$this->updatedRecords} articles updated, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";
            
            return( true );
        }
    }
    
    /**
     * Moves trackbacks from their own table to the comments table
     */
    class BlogTrackbacksDataTransformer extends DatabaseDataTransformer
    {        
        function getNumSteps()
        {
            return( parent::getNumSteps( "trackbacks" ));
        }
    
        function perform()
        {
            $this->message = "<b>Updating trackbacks (step 4 of 9)</b><br/><br/>";        
        
            $query = "SELECT t.id AS id, a.blog_id AS blog_id, t.url AS url, t.title AS title, t.article_id as article_id,
                      t.excerpt AS excerpt, t.blog_name AS blog_name, t.date AS date 
                      FROM ".$this->dbPrefix."trackbacks t, ".$this->dbPrefix."articles a 
                      WHERE t.article_id = a.id";

            $result = $this->Execute( $query, $this->page, $this->itemsPerPage );
            if( !$result ) {
                $this->message .= "Error updating trackbacks.<br/>";
                return true;
            }
            if( $result->RecordCount() == 0 ) {
				$this->message .= "No more records to process";
				return( true );            
            }
            
            // process all trackbacks and insert them again to the comments table
            include_once( PLOG_CLASS_PATH."class/data/textfilter.class.php" );
            while( $row = $result->FetchRow()) {
                // build the insert query
                $insert = "INSERT INTO ".$this->dbPrefix."articles_comments
                           (article_id, blog_id, topic, text, date, user_email, user_url, user_name, parent_id, 
                           client_ip, send_notification, status, spam_rate, normalized_text, normalized_topic,
                           type) 
                           VALUES (".$row["article_id"].",".$row["blog_id"].",'".Db::qstr($row["title"])."','".
                                     Db::qstr($row["excerpt"])."','".$row["date"]."','','".$row["url"]."','".
                                     Db::qstr($row["blog_name"])."', '0', '0.0.0.0','0', '0', '0','".
                                     Textfilter::urlize( $row["excerpt"] )."','".
                                     Textfilter::urlize( $row["title"] )."','2')";                            
                
                $insertRes = $this->Execute( $insert );
                if( $insertRes )
                    $this->updatedRecords++;
                else
                    $this->message .= "Error adding trackback with id ".$row["id"]."<br/>";
            }
            $result->Close();
            
            $processed = $this->page * $this->itemsPerPage;            
            $this->message .= "{$this->updatedRecords} trackbacks modified, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";            
        
            return true;        
        }
    }
    
    /**
     * Updates the blog counters (number of posts, number of comments, etc)
     */
    class BlogCountersDataTransformer extends DatabaseDataTransformer 
    {
        function getNumSteps()
        {
            return( parent::getNumSteps( "blogs" ));
        }
    
        function perform()
        {
            $this->message = "<b>Updating blogs (step 1 of 9)</b><br/><br/>";
                                
            // list of blog ids
            $query4 = "SELECT id, blog, owner_id FROM ".$this->dbPrefix."blogs";            
            $res4 = $this->Execute( $query4, $this->page, $this->itemsPerPage );
            $blogIds = Array();
			if( $res4->RecordCount() == 0 ) {
				$this->message .= "No more records to process";
				return( true );
			}
            while( $row = $res4->FetchRow()) {
            	// gather the blog ids and use them to reduce the amount of data that the other queries need to process            
            	$blogIds[] = $row["id"];
            }
            $where = "(".implode( $blogIds, "," ).")";

            // number of active articles
            $query1 = "SELECT blog_id, COUNT(*) AS total FROM ".$this->dbPrefix."articles ".
                      "WHERE status = ".POST_STATUS_PUBLISHED." AND blog_id IN {$where} GROUP BY blog_id";
            // number of active comments
            $query2 = "SELECT a.blog_id AS blog_id, COUNT(*) AS total FROM ".$this->dbPrefix."articles_comments c, ".
                      $this->dbPrefix."articles a WHERE a.id = c.article_id AND a.status = ".POST_STATUS_PUBLISHED." ".
                      "AND a.blog_id IN {$where} GROUP BY a.blog_id";
            // number of trackbacks
            $query3 = "SELECT blog_id, COUNT(*) AS total FROM ".$this->dbPrefix."trackbacks t,".
                      $this->dbPrefix."articles a WHERE a.id = t.article_id ".
                      "AND blog_id IN {$where} GROUP BY a.blog_id";
            // create_date and last_update_date
            $query5 = "SELECT blog_id, MIN(date) AS create_date, MAX(date) AS last_update_date
                       FROM ".$this->dbPrefix."articles WHERE blog_id IN {$where} GROUP BY blog_id";                     
                      
            // execute the 1st query
            $res1 = $this->Execute( $query1 );
            if( !$res1 ) {
                $this->message .= "Error performing changes to the blogs table";
                return false;
            }
            $numArticles = Array();
            while( $row = $res1->FetchRow()) {
                $numArticles[$row["blog_id"]] = $row["total"];
            }
            $res1->Close();
                        
            // total number of active comments
            $res2 = $this->Execute( $query2 );
            if( !$res2 ) {
                $this->message .= "Error performing changes to the blogs table";
                return false;
            }
            $numComments = Array();
            while( $row = $res2->FetchRow()) {
                $numComments[$row["blog_id"]] = $row["total"];
            }
            $res2->Close();
            
            // total number of trackbacks
            $res3 = $this->Execute( $query3 );
            if( !$res3 ) {
                $this->message .= "Error performing changes to the blogs table";
                return false;
            }
            $numTrackbacks = Array();
            while( $row = $res3->FetchRow()) {
                $numTrackbacks[$row["blog_id"]] = $row["total"];
            }
            $res3->Close();
            
            // update and create dates
            $res5 = $this->Execute( $query5 );
            if( !$res5 ) {
                $this->message .= "Error performing changes to the blogs table";
                return false;
            }
            $createDates = Array();
            $lastUpdateDates = Array();
            while( $row = $res5->FetchRow()) {
                $createDates[$row["blog_id"]] = $row["create_date"];
                $lastUpdateDates[$row["blog_id"]] = $row["last_update_date"];
            }
            $res5->Close();            

            foreach( $blogIds as $blogId ) {
                // load the counters
                isset( $numArticles[$blogId] ) ? $totalArticles = $numArticles[$blogId] : $totalArticles = 0;
                isset( $numComments[$blogId] ) ? $totalComments = $numComments[$blogId] : $totalComments = 0;
                isset( $numTrackbacks[$blogId] ) ? $totalTrackbacks = $numTrackbacks[$blogId] :$totalTrackbacks = 0;
                
                if( isset( $createDates[$blogId] ))
                    $createDate = $createDates[$blogId];
                else {
                    $t = new Timestamp();
                    $createDate = $t->getTimestamp();
                }
                if( isset( $lastUpdateDates[$blogId] ))
                    $lastUpdateDate = $lastUpdateDates[$blogId];
                else {
                    $t = new Timestamp();
                    $lastUpdateDate = $t->getTimestamp();
                }
                                
                // build the update query
                $query = "UPDATE ".$this->dbPrefix."blogs SET num_posts = {$totalArticles},
                          num_comments = {$totalComments},
                          num_trackbacks = {$totalTrackbacks},
                          create_date = '{$createDate}',
                          last_update_date = '{$lastUpdateDate}'
                          WHERE id = {$blogId}";
                          
                // and execute it
                $result = $this->Execute( $query );
                if( !$result ) {
                    $this->message .= "Error updating blog with id {$catId}<br/>";
                }
                else
                    $this->updatedRecords++;
            }
            $res4->Close();
            
            $processed = $this->page * $this->itemsPerPage;            
            $this->message .= "{$this->updatedRecords} blogs updated, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";
            return true;         
        } 
    }
    
    /**
     * populates the site_admin field in the users table
     */
    class AdminUsersDataTransformer extends DatabaseDataTransformer
    {
        function getNumSteps()
        {
            return( parent::getNumSteps( "users_permissions" ));
        }
    
        function perform()
        {
            $this->message = "<b>Updating users (step 5 of 9)</b><br/><br/>";        
        
            $query1 = "SELECT DISTINCT user_id FROM ".$this->dbPrefix."users_permissions WHERE permission_id = 1";

            // total number of comments
            $res1 = $this->Execute( $query1, $this->page, $this->itemsPerPage );
            if( !$res1 ) {
                $this->message .= "Error performing changes to the users table";
                return false;
            }
            if( $res1->RecordCount() == 0 ) {
				$this->message .= "No more records to process";
				return( true );            
            }
            $numComments = Array();
            while( $row = $res1->FetchRow()) {
                $userId = $row["user_id"];
                $update = "UPDATE ".$this->dbPrefix."users SET site_admin = 1 WHERE id = ".Db::qstr($userId);
                $result = $this->Execute( $update );
                if( !$result ) {
                    $this->message .= "Error updating user with id {$userId}<br/>";
                }
                else
                    $this->updatedRecords++;
            }
            $res1->Close();
            $this->message .= "{$this->updatedRecords} users updated, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";
            return true;        
        }
        
        
    }
    
    /**
     * populates the article categories counter
     */
    class LinkCategoriesDataTransformer extends DatabaseDataTransformer
    {
        function getNumSteps()
        {
            return( parent::getNumSteps( "mylinks_categories" ));
        }           
    
        function perform()
        {
            $this->message = "<b>Updating link categories (step 6 of 9)</b><br/><br/>";        
        
            // load each one of the categories and update them
            // list of categories
            $query2 = "SELECT id FROM ".$this->dbPrefix."mylinks_categories";            
            $res2 = $this->Execute( $query2, $this->page, $this->itemsPerPage );
            $catIds = Array();
            if( $res2->RecordCount() == 0 ) {
				$this->message .= "No more records to process";
				return( true );
            }
            while( $row = $res2->FetchRow()) {
            	$catIds[] = $row["id"];
            }
            $where = "(".implode( $catIds, "," ).")";
            
            // total number of articles
            $query1 = "SELECT category_id, COUNT(*) AS total FROM ".$this->dbPrefix."mylinks ".
                      "WHERE category_id IN {$where} GROUP BY category_id";
                      
            // execute the 1st query
            $res1 = $this->Execute( $query1 );
            if( !$res1 ) {
                $this->message .= "Error performing changes to the link categories table";
                return false;
            }
            $numArticles = Array();
            while( $row = $res1->FetchRow()) {
                $numLinks[$row["category_id"]] = $row["total"];
            }
            $res1->Close();                       
            
            foreach( $catIds as $catId ) {
                // load the counters
                isset( $numLinks[$catId] ) ? $totalLinks = $numLinks[$catId] : $totalLinks = 0;
                
                // build the update query
                $query = "UPDATE ".$this->dbPrefix."mylinks_categories SET num_links = {$totalLinks}, last_modification = last_modification
                          WHERE id = {$catId}";
                          
                // and execute it
                $result = $this->Execute( $query );
                if( !$result ) {
                    $this->message .= "Error updating links category with id {$catId}<br/>";
                }
                else
                    $this->updatedRecords++;
            }
            $res2->Close();
            
            $processed = $this->page * $this->itemsPerPage;            
            $this->message .= "{$this->updatedRecords} links categories updated, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";
            return true;        
        }    
    }
    
    /**
     * populates some fields in the comments table
     */
    class CommentsDataTransformer extends DatabaseDataTransformer
    {                
        function getNumSteps()
        {
            return( parent::getNumSteps( "articles_comments" ));
        }          
            
        function perform()
        {        
            $this->message = "<b>Updating article comments (step 7 of 9)</b><br/><br/>";
        
            // now process all the comments
            $query2 = "SELECT id FROM ".$this->dbPrefix."articles_comments";
            $res2 = $this->Execute( $query2, $this->page, $this->itemsPerPage );
            if( !$res2 ) {
                $this->message.= "Error loading comments<br/>";
                return false;
            }
            if( $res2->RecordCount() == 0 ) {
				$this->message .= "No more records to process";
				return( true );            
            }
            $commentIds = Array();
            while( $row = $res2->FetchRow()) {
            	$commentIds[] = $row["id"];
            }
            $where = "(".implode( $commentIds, "," ).")";
            
            // build the query
            $query1 = "SELECT a.blog_id AS blog_id, c.id AS comment_id
                       FROM ".$this->dbPrefix."articles a, ".$this->dbPrefix."articles_comments c
                       WHERE a.id = c.article_id AND c.id IN {$where}"; 
            $res1 = $this->Execute( $query1 );
            if( !$res1 ) {
                $this->message .= "Error loading comments<br/>";
                return false;
            }
            $commentBlogIds = Array();
            while( $row = $res1->FetchRow()) {
                $commentBlogIds[$row["comment_id"]] = $row["blog_id"];
            }
            $res1->Close();                       
            
            foreach( $commentIds as $commentId ) {
                // build the query      
                $blogId = $commentBlogIds[$commentId];
                $query = "UPDATE ".$this->dbPrefix."articles_comments
                          SET blog_id = {$blogId}, date = date WHERE id = {$commentId}";
                // and execute it
                $res = $this->Execute( $query );
                if( !$res )
                    $this->message .= "Error updating comment with id {$commentId}<br/>";
                else
                    $this->updatedRecords++;
            }
            $res2->Close();
            
            $processed = $this->page * $this->itemsPerPage;            
            $this->message .= "{$this->updatedRecords} comments updated, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";
        	 
        	return true;        
        }
    }
    
    /**
     * populates album counters
     */
    class AlbumsDataTransformer extends DatabaseDataTransformer
    {            
        function getNumSteps()
        {
            return( parent::getNumSteps( "gallery_albums" ));
        }  
        
        function perform()
        {
            $this->message = "<b>Updating resource albums (step 8 of 9)</b><br/><br/>";        
        
            // now update all albums
            $query3 = "SELECT id FROM ".$this->dbPrefix."gallery_albums";
            $res3 = $this->Execute( $query3, $this->page, $this->itemsPerPage );
            $albumIds = Array();
            if( !$res3 ) {
                $this->message .= "Error updating gallery albums<br/>";
                return false;
            }
            if( $res3->RecordCount() == 0 ) {
				$this->message .= "No more records to process";
				return( true );            
            }
            $albumIds = Array();
            while( $row = $res3->FetchRow()) {
            	$albumIds[] = $row["id"];
            }
            $where = "(".implode( $albumIds, "," ).")";
            
            // load the resource counter
        	$query1 = "SELECT album_id, COUNT(*) AS total FROM ".$this->dbPrefix."gallery_resources ".
        	          "WHERE album_id IN {$where} GROUP BY album_id";
        	// and the child counter
        	$query2 = "SELECT parent_id, COUNT(*) AS total FROM ".$this->dbPrefix."gallery_albums 
        	           WHERE parent_id > 0 AND id IN {$where} GROUP BY parent_id";
        	           
            $res1 = $this->Execute( $query1 );
            if( !$res1 ) {
                $this->message .= "Error loading resources<br/>";
                return false;
            }
            $numResources = Array();
            while( $row = $res1->FetchRow()) {
                $numResources[$row["album_id"]] = $row["total"];
            }
            $res1->Close();
            
            $res2 = $this->Execute( $query2 );
            if( !$res2 ) {
                $this->message .= "Error loading albums<br/>";
                return false;
            }
            $numChildren = Array();
            while( $row = $res2->FetchRow()) {
                $numChildren[$row["parent_id"]] = $row["total"];
            }
            $res2->Close();            
            
            foreach( $albumIds as $albumId ) {
                isset( $numResources[$albumId] ) ? $resources = $numResources[$albumId] : $resources = 0;
                isset( $numChildren[$albumId]) ? $children = $numChildren[$albumId] : $children = 0;
                
                // build the query
                $query = "UPDATE ".$this->dbPrefix."gallery_albums
                          SET num_children = {$children}, num_resources = {$resources}, date = date
                          WHERE id = {$albumId}";
                // and execute it
                $res = $this->Execute( $query );
                if( !$res )
                    $this->message .= "Error updating gallery album with id {$albumId}<br/>";
                else
                    $this->updatedRecords++;
            }
            $res3->Close();
             
            $processed = $this->page * $this->itemsPerPage;
            $this->message .= "{$this->updatedRecords} gallery albums updated, ".$this->getTotalProcessedRecords()." processed so far (".$this->getPercentProcessed()."%)<br/>";
            
            return true;
        }
    }
    
    /**
     * drops a few fields and tables that are not needed anymore
     */
    class PostSchemaDataTransformer extends DatabaseDataTransformer
    {
        function getNumSteps()
        {
            // everything's done in one step
            return( 1 );
        }       
            
        function perform()
        {
            $this->message = "<b>Removing unneeded database fields and tables (step 9 of 9)</b><br/><br/>";        
        
        	global $Changes;
        	
            foreach( $Changes as $change ) {
                // replace the de prefix and base url
                $query = str_replace( "{dbprefix}", $this->dbPrefix, $change );
                $result = $this->Execute( $query  );
                if( !$result ) {
                	$this->message .= "Error updating database schema {$query}<br/>";
                }
            }
            
            $this->message .= "Database schema updated.<br/>";
            
            return true;        
        }
    }
    
    /**
     * This class is basically now a "data transformer runner", because now it works
     * like class that executes data transformers, collects their results and refreshes
     * the page to execute the next step of the transformer. If the current transformer
     * reported that its processing is complete, this class will continue with the next
     * transformer unless there are no more transformer to run.
     *
     * In order to coordinate the current step and the current transformer, two parameters
     * are needed in each request:
     *
     * - page
     * - transformerId
     *
     * The 'page' parameter holds the current page, while 'transformerId' is the index of 
     * the current transformer in the $this->transformers array.
     *
     * In order to add new transformers, follow these steps:
     *
     * - Create your own transfomer class by extending DatabaseDataTransformer and implementing
     * the methods DatabaseDataTransformer::perform() and DatabaseDataTransformer::getNumSteps(). The
     * first does the data processing while the second one returns the number of needed steps to the
     * class running the transformer.
     * - Add the name of the transformer class to the the UpdateStepThree::transformers array,
     * and the class will take care of everything else. 
     */
    class UpdateStepThree extends WizardPagedAction
    {
        var $message;    
        var $currentTransformerId;
    
        function UpdateStepThree( $actionInfo, $httpRequest )
        {
            $this->WizardPagedAction( $actionInfo, $httpRequest );
            
            /**
             * array with the data transformers that will be run
             */
            $this->transformers = Array(
                "BlogCountersDataTransformer",
                "CategoryCountersDataTransformer",
                "ArticleCountersDataTransformer",
                "BlogTrackbacksDataTransformer",
                "AdminUsersDataTransformer",
                "LinkCategoriesDataTransformer",
                "CommentsDataTransformer",
                "AlbumsDataTransformer",
                "PostSchemaDataTransformer"
            );
            
            $this->currentTransformerId = $this->getTransformerIdFromRequest();
        }
        
        /**
         * gets the id of the transformer from the request. If it is not available, it
         * will return the id of the first transformer available (which is '0')
         *
         * @private
         */
        function getTransformerIdFromRequest()
        {    	
			$id = HttpVars::getRequestValue( "transformerId" );
			$val = new IntegerValidator();
			if( !$val->validate( $id ))
				$id = 0;
							
			return $id;        
        }        

       function perform()
       {
            $step = $this->getPageFromRequest();
            
            // get the current transformer class so that we can continue where we left
            $transformerClass = $this->transformers[$this->currentTransformerId];                              
            $transformer = new $transformerClass( $step );
            $result = $transformer->perform();
            $complete = $transformer->isComplete();
            $message = $transformer->message;
            
            //print("transformer = $transformerClass<br/>");
             
            // error during processing and the processor is configured
            // to fail on error   
            if( !$result && $transformer->failOnError ) {
                //print("Error in step = $step<br/>");
                $this->_view = new WizardView( "update3" );
                // current and next step
                $this->_view->setValue( "currentStep", $step );
                $this->_view->setValue( "nextStep", $step+1 );                
                // whether this transformer is ready
                $this->_view->setValue( "complete", $complete );
                // transformer id
                $this->_view->setValue( "transformerId", $this->currentTransformerId );            
                $this->_view->setValue( "error", true );
                if( $transformer->DbError() != "" ) {
                    $message .= "<br/><br/>The database error message was: ".$transformer->DbError()."<br/>";
                }

                $this->_view->setErrorMessage( $message );
            }                
            else {
                if( !$complete ) {
                    //print("it's not complete! step = $step<br/>");
                    $this->_view = new WizardView( "update3" );
                    // current and next step
                    $this->_view->setValue( "currentStep", $step );
                    $this->_view->setValue( "nextStep", $step+1 );                
                    // whether this transformer is ready
                    $this->_view->setValue( "complete", $complete );
                    // transformer id
                    $this->_view->setValue( "transformerId", $this->currentTransformerId );
                }            
                else {
                    // have we already been through all transformers?
                    //print("transformer complete! - num transformers = ".count($this->transformers)."<br/>");
                    $moreTransformers = ( $this->currentTransformerId+1 < count( $this->transformers ));
                    if( $moreTransformers ) {
                        //print("Starting new transformer!<br/>");
                        $this->_view = new WizardView( "update3" );
                        // current and next step
                        $this->_view->setValue( "currentStep", 0 );
                        $this->_view->setValue( "nextStep", 1 );  
                        // whether this transformer is ready
                        $this->_view->setValue( "complete", false );
                        // transformer id
                        $this->_view->setValue( "transformerId", $this->currentTransformerId+1 );
                    }
                    else {
                        // no more data to transform, we can finalize the installation!
                        $this->_view = new WizardView( "update4" );
                    }
                }
            }
            
            $this->_view->setValue( "message", $message );            

            return true;
        }

    }    

    // check if the "./tmp" folder is writable by us, otherwise
    // throw an error before the user gets countless errors
    // from Smarty
    if( !File::isWritable( TEMP_FOLDER ) || !File::isDir( TEMP_FOLDER )) {
        print("<span style=\"color:red; font-size: 14px;\">Error</span><br/><br/>This wizard needs the ".TEMP_FOLDER." folder to be writable by the web server user.<br/><br/>Please correct it and try again.");
        die();
    }

    //// main part ////
    $controller = new Controller( $_actionMap, "nextStep" );
    $controller->process( HttpVars::getRequest());
?>
