diff options
Diffstat (limited to 'includes/classes/BitInstaller.php')
| -rw-r--r-- | includes/classes/BitInstaller.php | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/includes/classes/BitInstaller.php b/includes/classes/BitInstaller.php new file mode 100644 index 0000000..7795403 --- /dev/null +++ b/includes/classes/BitInstaller.php @@ -0,0 +1,729 @@ +<?php +/** + * @version $Header$ + * @package install + */ + +/** + * @package install + */ +class BitInstaller extends BitSystem { + + /** + * mPackageUpgrades + * + * @var array + * @access public + */ + var $mPackageUpgrades = array(); + + /** + * mRequirements + * + * @var array + * @access public + */ + var $mRequirements = array(); + + /** + * Initiolize BitInstaller + * @access public + */ + function __construct() { + parent::__construct(); + $this->getWebServerUid(); + } + + /** + * loadAllUpgradeFiles load upgrade files from all packages that are installed + * + * @access public + * @return void + */ + function loadAllUpgradeFiles() { + foreach( array_keys( $this->mPackages ) as $pkg ) { + $this->loadUpgradeFiles( $pkg ); + } + } + + /** + * Minimal login just for install in case users tables have been modified + * + * @access public + * @return void + */ + function login( $pLogin, $pPassword, $pChallenge=NULL, $pResponse=NULL ) { + global $gBitUser; + + $isvalid = false; + + $loginCol = strpos( $pLogin, '@' ) ? 'email' : 'login'; + + if( $gBitUser->validate( $pLogin, $pPassword, $pChallenge, $pResponse ) ) { + $userInfo = $gBitUser->getUserInfo( array( $loginCol => $pLogin ) ); + + if( $userInfo['user_id'] != ANONYMOUS_USER_ID ) { + // User is valid and not due to change pass.. + $gBitUser->mUserId = $userInfo['user_id']; + $gBitUser->mInfo = $userInfo; + $gBitUser->loadPermissions( TRUE ); + + $sessionId = session_id(); + $gBitUser->sendSessionCookie( $sessionId ); + $gBitUser->updateSession( $sessionId ); + } + } + + return $gBitUser->isAdmin(); + } + + /** + * loadUpgradeFiles This will load all files in the dir <pckage>/admin/upgrades/<version>.php with a version greater than the one installed + * + * @param array $pPackage + * @access public + * @return void + */ + function loadUpgradeFiles( $pPackage ) { + if( !empty( $pPackage )) { + $dir = constant( strtoupper( $pPackage )."_PKG_PATH" )."admin/upgrades/"; + if( $this->isPackageActive( $pPackage ) && is_dir( $dir ) && $upDir = opendir( $dir )) { + while( FALSE !== ( $file = readdir( $upDir ))) { + if( is_file( $dir.$file )) { + $upVersion = str_replace( ".php", "", $file ); + // we only want to load files of versions that are greater than is installed + if( $this->validateVersion( $upVersion ) && version_compare( $this->getVersion( $pPackage ), $upVersion, '<' )) { + include_once( $dir.$file ); + } + } + } + } + } + } + + /** + * registerPackageUpgrade + * + * @param array $pParams Hash of information about upgrade + * @param string $pParams[package] Name of package that is upgrading + * @param string $pParams[version] Version of this upgrade + * @param string $pParams[description] Description of what the upgrade does + * @param string $pParams[post_upgrade] Textual note of stuff that needs to be observed after the upgrade + * @param array $pUpgradeHash Hash of update rules. See existing upgrades on how this works. + * @access public + * @return void + */ + function registerPackageUpgrade( $pParams, $pUpgradeHash = array() ) { + if( $this->verifyPackageUpgrade( $pParams )) { + $this->registerPackageVersion( $pParams['package'], $pParams['version'] ); + $this->mPackageUpgrades[$pParams['package']][$pParams['version']] = $pParams; + $this->mPackageUpgrades[$pParams['package']][$pParams['version']]['upgrade'] = $pUpgradeHash; + + // sort everything for a nice display + ksort( $this->mPackageUpgrades ); + uksort( $this->mPackageUpgrades[$pParams['package']], 'version_compare' ); + } + } + + /** + * verifyPackageUpgrade + * + * @param array $pParams Hash of information about upgrade + * @param string $pParams[package] Name of package that is upgrading + * @param string $pParams[version] Version of this upgrade + * @param string $pParams[description] Description of what the upgrade does + * @param string $pParams[post_upgrade] Textual note of stuff that needs to be observed after the upgrade + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function verifyPackageUpgrade( &$pParams ) { + if( empty( $pParams['package'] )) { + $this->mErrors['package'] = "Please provide a valid package name."; + } else { + $pParams['package'] = strtolower( $pParams['package'] ); + } + + if( empty( $pParams['version'] ) || !$this->validateVersion( $pParams['version'] )) { + $this->mErrors['version'] = "Please provide a valid version number."; + } elseif( empty( $this->mErrors ) && !empty( $this->mPackageUpgrades[$pParams['package']][$pParams['version']] )) { + $this->mErrors['version'] = "Please make sure you use a unique version number to register your new database changes."; + } + + if( empty( $pParams['description'] )) { + $this->mErrors['description'] = "Please add a brief description of what this upgrade is all about."; + } + + // since this should only show up when devs are working, we'll simply display the output: + if( !empty( $this->mErrors )) { + vd( $this->mErrors ); + bt(); + } + + return( count( $this->mErrors ) == 0 ); + } + + /** + * registerUpgrade + * + * @param array $pPackage + * @param array $pUpgradeHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function registerUpgrade( $pPackage, $pUpgradeHash ) { + $pPackage = strtolower( $pPackage ); // lower case for uniformity + if( !empty( $pUpgradeHash ) ) { + $this->mUpgrades[$pPackage] = $pUpgradeHash; + } + } + + /** + * display + * + * @param string $pTemplate + * @param string $pBrowserTitle + * @access public + * @return void + */ + function in_display( $pPackage, $pTemplate ) { + header( 'Content-Type: text/html; charset=utf-8' ); + if( ini_get( 'safe_mode' ) && ini_get( 'safe_mode_gid' )) { + umask( 0007 ); + } + // force the session to close *before* displaying. Why? Note this very important comment from http://us4.php.net/exec + session_write_close(); + + if( !empty( $pPackage ) ) { + $this->setBrowserTitle( $pPackage ); + } + global $gBitSmarty; + $gBitSmarty->verifyCompileDir(); + $gBitSmarty->display( $pTemplate ); + } + + /** + * isInstalled + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function isInstalled( $pPackage = 'kernel' ) { + return( !empty( $this->mPackages[$pPackage]['installed'] )); + } + + /** + * getWebServerUid set global wwwuser and wwwgroup + * + * @access public + * @return void + */ + function getWebServerUid() { + global $wwwuser, $wwwgroup; + $wwwuser = $wwwgroup = ''; + + if( is_windows() ) { + $wwwuser = 'SYSTEM'; + $wwwgroup = 'SYSTEM'; + } + + if( function_exists( 'posix_getuid' )) { + $user = @posix_getpwuid( @posix_getuid() ); + $group = @posix_getpwuid( @posix_getgid() ); + $wwwuser = $user ? $user['name'] : false; + $wwwgroup = $group ? $group['name'] : false; + } + + if( !$wwwuser ) { + $wwwuser = 'nobody (or the user account the web server is running under)'; + } + + if( !$wwwgroup ) { + $wwwgroup = 'nobody (or the group account the web server is running under)'; + } + } + + /** + * getTablePrefix + * + * @access public + * @return database adjusted table prefix + */ + function getTablePrefix() { + global $gBitDbType; + $ret = BIT_DB_PREFIX; + // avoid errors in ADONewConnection() (wrong database driver etc...) + // strip out some schema stuff + switch( $gBitDbType ) { + case "sybase": + // avoid database change messages + ini_set('sybct.min_server_severity', '11'); + break; + case "oci8": + case "postgres": + // Do a little prep work for postgres, no break, cause we want default case too + if( preg_match( '/\./', $ret ) ) { + // Assume we want to dump in a schema, so set the search path and nuke the prefix here. + $schema = preg_replace( '/`/', '"', substr( $ret, 0, strpos( $ret, '.' )) ); + $quote = strpos( $schema, '"' ); + if( $quote !== 0 ) { + $schema = '"'.$schema; + } + // set scope to current schema + $result = $this->mDb->query( "SET search_path TO $schema" ); + // return everything after the prefix + $ret = substr( BIT_DB_PREFIX, strrpos( BIT_DB_PREFIX, '`' ) + 1 ); + } + break; + } + return $ret; + } + + /** + * upgradePackage + * + * @param array $pPackage + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function upgradePackage( $pPackage ) { + if( !empty( $pPackage ) && !empty( $this->mUpgrades[$pPackage] )) { + return( $this->applyUpgrade( $pPackage, $this->mUpgrades[$pPackage] )); + } + } + + /** + * upgradePackageVersion + * + * @param array $pPackage + * @param array $pVersion + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function upgradePackageVersions( $pPackage ) { + if( !empty( $pPackage ) && !empty( $this->mPackageUpgrades[$pPackage] )) { + // make sure everything is in the right order + uksort( $this->mPackageUpgrades[$pPackage], 'upgrade_version_sort' ); + + foreach( array_keys( $this->mPackageUpgrades[$pPackage] ) as $version ) { + // version we are upgrading from + $this->mPackageUpgrades[$pPackage][$version]['from_version'] = $this->getVersion( $pPackage ); + + // apply upgrade + $errors[$version] = $this->applyUpgrade( $pPackage, $this->mPackageUpgrades[$pPackage][$version]['upgrade'] ); + if( !empty( $errors[$version] )) { + return $errors; + } else { + // if the upgrade ended without incidence, we store the package version. + // this way any successfully applied upgrade can only be applied once. + $this->storeVersion( $pPackage, $version ); + } + } + } + + return NULL; + } + + /** + * applyUpgrade + * + * @param array $pPackage + * @param array $pUpgradeHash + * @access public + * @return empty array on success, array with errors on failure + */ + function applyUpgrade( $pPackage, $pUpgradeHash ) { + global $gBitDb, $gBitDbType; + $ret = array(); + + if( !empty( $pUpgradeHash ) && is_array( $pUpgradeHash )) { + // set table prefixes and handle special case of sequence prefixes + $schemaQuote = strrpos( BIT_DB_PREFIX, '`' ); + $sequencePrefix = ( $schemaQuote ? substr( BIT_DB_PREFIX, $schemaQuote + 1 ) : BIT_DB_PREFIX ); + $tablePrefix = $this->getTablePrefix(); + $dict = NewDataDictionary( $gBitDb->mDb ); + $failedcommands = array(); + + for( $i = 0; $i < count( $pUpgradeHash ); $i++ ) { + if( !is_array( $pUpgradeHash[$i] ) ) { + vd( "[$pPackage][$i] is NOT an array" ); + vd( $pUpgradeHash[$i] ); + bt(); + die; + } + + $type = key( $pUpgradeHash[$i] ); + $step = &$pUpgradeHash[$i][$type]; + + switch( $type ) { + case 'DATADICT': + for( $j = 0; $j < count( $step ); $j++ ) { + $dd = &$step[$j]; + switch( key( $dd ) ) { + case 'CREATE': + foreach( $dd as $create ) { + foreach( array_keys( $create ) as $tableName ) { + $completeTableName = $tablePrefix.$tableName; + $sql = $dict->CreateTableSQL( $completeTableName, $create[$tableName], 'REPLACE' ); + if( $sql && ( $dict->ExecuteSQLArray( $sql, FALSE ) > 0 ) ) { + } else { + $errors[] = 'Failed to create '.$completeTableName; + $failedcommands[] = implode( " ", $sql ); + } + } + } + break; + case 'ALTER': + foreach( $dd as $alter ) { + foreach( array_keys( $alter ) as $tableName ) { + $completeTableName = $tablePrefix.$tableName; + $this->mDb->convertQuery( $completeTableName ); + foreach( $alter[$tableName] as $from => $flds ) { + if( is_string( $flds )) { + $sql = $dict->ChangeTableSQL( $completeTableName, $flds ); + } else { + $sql = $dict->ChangeTableSQL( $completeTableName, array( $flds )); + } + + if( $sql ) { + for( $sqlIdx = 0; $sqlIdx < count( $sql ); $sqlIdx++ ) { + $this->mDb->convertQuery( $sqlFoo ); + } + } + + if( $sql && $dict->ExecuteSQLArray( $sql, FALSE ) > 0 ) { + } else { + $errors[] = 'Failed to alter '.$completeTableName.' -> '.$alter[$tableName]; + $failedcommands[] = implode( " ", $sql ); + } + } + } + } + break; + case 'RENAMETABLE': + foreach( $dd as $rename ) { + foreach( array_keys( $rename ) as $tableName ) { + $completeTableName = $tablePrefix.$tableName; + if( $sql = @$dict->RenameTableSQL( $completeTableName, $tablePrefix.$rename[$tableName] ) ) { + foreach( $sql AS $query ) { + $this->mDb->query( $query ); + } + } else { + $errors[] = 'Failed to rename table '.$completeTableName.'.'.$rename[$tableName][0].' to '.$rename[$tableName][1]; + $failedcommands[] = implode( " ", $sql ); + } + } + } + break; + case 'RENAMECOLUMN': + foreach( $dd as $rename ) { + foreach( array_keys( $rename ) as $tableName ) { + $completeTableName = $tablePrefix.$tableName; + foreach( $rename[$tableName] as $from => $flds ) { + // MySQL needs the fields string, others do not. + // see http://phplens.com/lens/adodb/docs-datadict.htm + $to = substr( $flds, 0, strpos( $flds, ' ') ); + if( $sql = @$dict->RenameColumnSQL( $completeTableName, $from, $to, $flds ) ) { + foreach( $sql AS $query ) { + $this->mDb->query( $query ); + } + } else { + $errors[] = 'Failed to rename column '.$completeTableName.'.'.$rename[$tableName][0].' to '.$rename[$tableName][1]; + $failedcommands[] = implode( " ", $sql ); + } + } + } + } + break; + case 'CREATESEQUENCE': + foreach( $dd as $create ) { + foreach( $create as $sequence ) { + $this->mDb->CreateSequence( $sequencePrefix.$sequence ); + } + } + break; + case 'RENAMESEQUENCE': + foreach( $dd as $rename ) { + foreach( $rename as $from => $to ) { + if( $gBitDbType != 'mysql' || $this->mDb->tableExists( $tablePrefix.$from ) ) { + if( $id = $this->mDb->GenID( $from ) ) { + $this->mDb->DropSequence( $sequencePrefix.$from ); + $this->mDb->CreateSequence( $sequencePrefix.$to, $id ); + } else { + $errors[] = 'Failed to rename sequence '.$sequencePrefix.$from.' to '.$sequencePrefix.$to; + $failedcommands[] = implode( " ", $sql ); + } + } else { + $this->mDb->CreateSequence( $sequencePrefix.$to, $pUpgradeHash['sequences'][$to]['start'] ); + } + } + } + break; + case 'DROPSEQUENCE': + foreach( $dd as $drop ) { + foreach( $drop as $sequence ) { + $this->mDb->DropSequence( $sequencePrefix.$sequence ); + } + } + break; + case 'DROPCOLUMN': + foreach( $dd as $drop ) { + foreach( array_keys( $drop ) as $tableName ) { + $completeTableName = $tablePrefix.$tableName; + foreach( $drop[$tableName] as $col ) { + if( $sql = $dict->DropColumnSQL( $completeTableName, $col ) ) { + foreach( $sql AS $query ) { + $this->mDb->query( $query ); + } + } else { + $errors[] = 'Failed to drop column '.$completeTableName; + $failedcommands[] = implode( " ", $sql ); + } + } + } + } + break; + case 'DROPTABLE': + foreach( $dd as $drop ) { + foreach( $drop as $tableName ) { + $completeTableName = $tablePrefix.$tableName; + $sql = $dict->DropTableSQL( $completeTableName ); + if( $sql && $dict->ExecuteSQLArray( $sql ) > 0 ) { + } else { + $errors[] = 'Failed to drop table '.$completeTableName; + $failedcommands[] = implode( " ", $sql ); + } + } + } + break; + case 'CREATEINDEX': + foreach( $dd as $indices ) { + foreach( array_keys( $indices ) as $index ) { + $completeTableName = $tablePrefix.$indices[$index][0]; + if( $sql = $dict->CreateIndexSQL( $index, $completeTableName, $indices[$index][1], $indices[$index][2] ) ) { + foreach( $sql AS $query ) { + $this->mDb->query( $query ); + } + } else { + $errors[] = 'Failed to create index '.$index; + $failedcommands[] = implode( " ", $sql ); + } + } + } + break; + } + } + if( !empty( $sql ) ) $sql = null; + break; + case 'QUERY': + uksort( $step, 'upgrade_query_sort' ); + foreach( array_keys( $step ) as $dbType ) { + if( $dbType == 'MYSQL' && preg_match( '/mysql/', $gBitDbType )) { + $sql = $step[$dbType]; + unset( $step['SQL92'] ); + } elseif( $dbType == 'PGSQL' && preg_match( '/postgres/', $gBitDbType )) { + $sql = $step[$dbType]; + unset( $step['SQL92'] ); + } elseif( $dbType == 'SQL92' && !empty( $step['SQL92'] )) { + $sql = $step[$dbType]; + } + + if( !empty( $sql ) ) { + foreach( $sql as $query ) { + if( !$result = $this->mDb->query( $query )) { + $errors[] = 'Failed to execute SQL query'; + $failedcommands[] = implode( " ", $sql ); + } + } + $sql = NULL; + } + } + break; + case 'PHP': + eval( $step ); + break; + case 'POST': + $postSql[] = $step; + break; + } + } + + // turn on features that are turned on + // legacy stuff + if( $this->isFeatureActive( 'feature_'.$pPackage )) { + $this->storeConfig( 'package_'.$pPackage, 'y', KERNEL_PKG_NAME ); + } + + if( !empty( $failedcommands )) { + $ret['errors'] = $errors; + $ret['failedcommands'] = $failedcommands; + } + } + + return $ret; + } + + /** + * identifyBlobs + * + * @param array $result + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function identifyBlobs( $result ) { + $blobs = array(); + //echo "FieldCount: ".$result->FieldCount()."\n"; + for( $i = 0; $i < $result->FieldCount(); $i++ ) { + $field = $result->FetchField($i); + //echo $i."-".$field->name."-".$result->MetaType($field->type)."-".$field->max_length."\n"; + // check for blobs + if(( $result->MetaType( $field->type ) == 'B' ) || ( $result->MetaType( $field->type )=='X' && $field->max_length >= 16777215 )) + $blobs[] = $field->name; + } + return $blobs; + } + + /** + * convertBlobs enumerate blob fields and encoded + * + * @param string $gDb + * @param array $res + * @param array $blobs + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function convertBlobs( $gDb, &$res, $blobs ) { + foreach( $blobs as $blob ) { + $res[$blob] = $gDb->dbByteEncode( $res[$blob] ); + } + } + + /** + * hasAdminBlock + * + * @access public + * @return TRUE on success, FALSE on failure + * @deprecated i think this isn't used any more + */ + function hasAdminBlock() { + deprecated( "i think this isn't used anymore." ); + global $gBitUser; + // Let's find out if we are have admin perm or a root user + $ret = TRUE; + if( empty( $gBitUser ) || $gBitUser->isAdmin() ) { + $ret = FALSE; + } else { + // let's try to load up user_id - if successful, we know we have one. + $rootUser = new BitPermUser( 1 ); + $rootUser->load(); + if( !$rootUser->isValid() ) { + $ret = FALSE; + } + } + return $ret; + } +} + +/** + * check_session_save_path + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function check_session_save_path() { + global $errors; + if( ini_get( 'session.save_handler' ) == 'files' ) { + $save_path = ini_get( 'session.save_path' ); + + if( !is_dir( $save_path )) { + $errors .= "The directory '$save_path' does not exist or PHP is not allowed to access it (check session.save_path or open_basedir entries in php.ini).\n"; + } elseif( !bw_is_writeable( $save_path )) { + $errors .= "The directory '$save_path' is not writeable.\n"; + } + + if( $errors ) { + $save_path = tempdir(); + + if (is_dir($save_path) && bw_is_writeable($save_path)) { + ini_set('session.save_path', $save_path); + + $errors = ''; + } + } + } +} + +/** + * makeConnection + * + * @param string $gBitDbType + * @param string $gBitDbHost + * @param string $gBitDbUser + * @param string $gBitDbPassword + * @param string $gBitDbName + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function makeConnection( $gBitDbType, $gBitDbHost, $gBitDbUser, $gBitDbPassword, $gBitDbName ) { + $gDb = &ADONewConnection( $gBitDbType ); + if( !$gDb->Connect( $gBitDbHost, $gBitDbUser, $gBitDbPassword, $gBitDbName )) { + echo $gDb->ErrorMsg()."\n"; + die; + } + global $gBitDbCaseSensitivity; + $gDb->mCaseSensitive = $gBitDbCaseSensitivity; + $gDb->SetFetchMode( ADODB_FETCH_ASSOC ); + return $gDb; +} + +/** + * upgrade_package_sort sort packages before they are upgraded + * + * @param string $a + * @param string $b + * @access public + * @return numeric sort direction + */ +function upgrade_package_sort( $a, $b ) { + global $gBitInstaller; + $aa = $gBitInstaller->mPackages[$a]; + $bb = $gBitInstaller->mPackages[$b]; + if(( $aa['required'] && $bb['required'] ) || ( !$aa['required'] && !$bb['required'] )) { + return 0; + } elseif( $aa['required'] && !$bb['required'] ) { + return -1; + } elseif( !$aa['required'] && $bb['required'] ) { + return 1; + } +} + +/** + * upgrade_version_sort sort upgrades based on version number + * + * @param string $a + * @param string $b + * @access public + * @return numeric sort direction + */ +function upgrade_version_sort( $a, $b ) { + return version_compare( $a, $b, '>' ); +} + +/** + * upgrade_query_sort sort queries that SQL92 queries are called last + * + * @param string $a + * @param string $b + * @access public + * @return numeric sort direction + */ +function upgrade_query_sort( $a, $b ) { + if( $a == 'SQL92' ) { + return 1; + } elseif( $b == 'SQL92' ) { + return -1; + } else { + return 0; + } +} + +?> |
