--- /dev/null
+<?php
+/*
+A class to upgrade the database schema automagically
+for MediaWIki itself, as well as for extensions
+(c) 2006 by Magnus Manske
+Released under GPL
+
+USAGE:
+
+First, copy AdminSettings.sample to AdminSettings.php and set the correct values there.
+Then, set
+ $wgAllowUpgrader = true ;
+in LocalSettings.php
+
+
+In your extension, add something like:
+$wgUpgrader->setUpgradeFunction ( "MyExtension" , "MyExtensionUpgradeFunction" ) ;
+
+function MyExtensionUpgradeFunction ( &$upgrader ) {
+
+ # Does this installation need to be upgraded to V1.2.3.4?
+ if ( $upgrader->needsUpgrade ( "MyExtension" , "1.2.3.4" ) ) {
+ $upgrader->startTransaction() ;
+ # Do something using the database
+ # $upgrader->dba
+ $upgrader->endTransaction() ;
+ }
+
+ # Repeat this for every upgradable version, lowest version first, highest last
+ # if ( $upgrader->needsUpgrade ( "MyExtension" , "1.2.3.5" ) ) { ... }
+
+}
+
+The upgrade function for MediaWiki itself is at the end of Setup.php
+
+*/
+
+class Upgrader {
+
+ var $data = array () ;
+ var $upgraders = array () ;
+ var $dba = NULL ; # Database, administration mode
+ var $data_loaded = false ;
+ var $db_transaction = false ;
+ var $dbr = NULL ;
+ var $dba = NULL ;
+
+ function loadVersionInfoFromDatabase () {
+ global $wgAllowUpgrader ;
+ if ( !$wgAllowUpgrader ) return ;
+ if ( $this->data_loaded ) return ;
+
+ $fname = "Upgrader::loadVersionInfoFromDatabase" ;
+ $this->dbr =& wfGetDB( DB_SLAVE );
+
+ # Upgrade to upgrader :-)
+ if ( !$this->dbr->tableExists ( "softwareversions" ) ) {
+ $this->prepareAdminDB () ;
+ $table = $this->dba->tableName( "softwareversions" );
+ $sql = "CREATE TABLE {$table} (
+ `sv_part` VARCHAR( 128 ) NOT NULL ,
+ `sv_version` VARCHAR( 128 ) NOT NULL ,
+ PRIMARY KEY ( `sv_part` )
+ );" ;
+ $this->dba->query ( $sql , $fname."-1" ) ;
+
+ # At this point, we wait until the table creation
+ # has progressed to the $dbr slave
+ while ( !$this->dbr->tableExists ( "softwareversions" ) ) {
+ sleep ( 1 ) ;
+ }
+ }
+
+ $res = $this->dbr->select (
+ "softwareversions" ,
+ "*" ,
+ array() ,
+ $fname."-2" ) ;
+
+ # Read existing versions
+ while ( $o = $this->dbr->fetchObject( $res ) ) {
+ $this->data[strtolower($o->sv_part)] = $this->makeVersionArray ( $o->sv_version ) ;
+ }
+ $this->dbr->freeResult( $res );
+
+ $this->data_loaded = true ;
+ }
+
+ /**
+ * Adds an upgrade function to the list, to be evoked by run() later
+ */
+ function setUpgradeFunction ( $part , $function ) {
+ $this->upgraders[$part] = $function ;
+ }
+
+ /**
+ * Evokes the upgrade functions
+ */
+ function run () {
+ global $wgAllowUpgrader ;
+ if ( !$wgAllowUpgrader ) return ;
+
+ # Running through the upgrading functions
+ foreach ( $this->upgraders AS $part => $function ) {
+ $function ( $this ) ;
+ }
+ }
+
+ /**
+ * Tells the caller if the given version is greater than the current one
+ @return bool TRUE if $part needs updating, FALSE otherwise
+ */
+ function needsUpgrade ( $part , $version ) {
+ $this->loadVersionInfoFromDatabase () ;
+ $currentversion = $this->getCurrentVersion ( $part ) ;
+ $this->lastcurrentversion = $currentversion ;
+ $this->lastpart = $part ;
+ $this->lastnewversion = $version ;
+ return $this->compareVersions ( $currentversion , $version ) ;
+ }
+
+ /**
+ * Initializes the transaction to update the database
+ */
+ function startTransaction () {
+ if ( $this->db_transaction ) # Already transaction in progress
+ return ;
+
+ # Open admin-access db
+ $this->prepareAdminDB() ;
+ $this->dba->begin() ; # Begin transaction
+ $this->db_transaction = true ;
+ }
+
+ /**
+ * Finalizes the transaction to update the database
+ * Does a last paranoia check
+ */
+ function endTransaction () {
+ if ( !$this->db_transaction )
+ return ;
+ if ( $this->dba == NULL )
+ return ;
+
+ $fname = "Upgrader::endTransaction" ;
+
+ if ( $this->stillCurrentVersion() ) {
+ # Version has not changed since initial lookup; so we're the only one updating, hopefully
+
+ # Set new version string
+ $v = $this->makeVersionArray ( $this->lastnewversion ) ;
+ $this->dba->delete (
+ "softwareversions",
+ array ( "sv_part" => $this->lastpart ) ,
+ $fname . "-DELETE"
+ ) ;
+ $this->dba->insert (
+ "softwareversions",
+ array (
+ "sv_part" => strtolower ( $this->lastpart ) ,
+ "sv_version" => implode ( "." , $v ) ,
+ ) ,
+ $fname . "-INSERT"
+ ) ;
+ $this->data[strtolower($this->lastpart)] = $v ;
+
+ $this->dba->commit($fname) ;
+ print $this->lastpart . " has been upgraded to version " . implode ( "." , $v ) . "<br/>\n" ;
+ } else {
+ # Version has changes, so someone probably did the update already
+ $this->dba->rollback($fname);
+ }
+ $this->db_transaction = false ;
+ }
+
+ /**
+ * Checks if the current version in the write table is still the same
+ * Multiple upgrading paranoia
+ @return bool
+ */
+ function stillCurrentVersion () {
+ # Check if this is really, *really* the correct version in the writing DB
+ # Using new database to go around the transaction
+ $dbw =& wfGetDB( DB_MASTER );
+ $res = $dbw->select (
+ "softwareversions" ,
+ "*" ,
+ array ( "sv_part" => $this->lastpart ) ,
+ "Upgrader::setLock" ) ;
+ $v = "0.0.0.0" ;
+ if ( $res != NULL ) {
+ if ( $o = $dbw->fetchObject( $res ) ) {
+ $dbw->freeResult( $res );
+ $v = $o->sv_version ;
+ }
+ }
+
+ # Are they unequal?
+ if ( $this->compareVersions ( $v , $this->lastcurrentversion ) OR
+ $this->compareVersions ( $this->lastcurrentversion , $v ) ) {
+ $this->error ( "Something funny happened on the way to the moon." ) ;
+ return false ;
+ }
+ return true ;
+ }
+
+ /**
+ * Open admin access to database
+ */
+ function prepareAdminDB () {
+ if ( $this->dba != NULL )
+ return ;
+
+ @include_once ( "AdminSettings.php" ) ;
+ if ( !isset ( $wgDBadminuser ) ) {
+ $this->error ( "You'll need to set up AdminSettings.php for automatic upgrades!" ) ;
+ exit ( 1 ) ;
+ }
+
+ global $wgDBserver , $wgDBname ;
+ $this->dba = new Database ( $wgDBserver , $wgDBadminuser , $wgDBadminpassword , $wgDBname ) ;
+ }
+
+ /**
+ * Get the current version of a part
+ @return array
+ */
+ function getCurrentVersion ( $part ) {
+ $part = strtolower ( $part ) ;
+ if ( isset ( $this->data[$part] ) ) {
+ return $this->data[$part] ;
+ } else {
+ # Not set yet, returning version 0.0.0.0
+ return $this->makeVersionArray ( "0.0.0.0" ) ;
+ }
+ }
+
+ /**
+ * Compares versions, style 1.2.3.4
+ * Takes arrays or strings (which will be automatically converted)
+ @return bool TRUE if $v1 < $v2
+ */
+ function compareVersions ( $v1 , $v2 ) {
+ if ( !is_array ( $v1 ) )
+ $v1 = $this->makeVersionArray ( $v1 ) ;
+ if ( !is_array ( $v2 ) )
+ $v2 = $this->makeVersionArray ( $v2 ) ;
+
+ # Compare versions
+ while ( count ( $v1 ) > 0 ) {
+ $a1 = array_shift ( $v1 ) ;
+ $a2 = array_shift ( $v2 ) ;
+ if ( $a1 < $a2 )
+ return true ;
+ if ( $a1 > $a2 )
+ return false ;
+ }
+ # Equal versions
+ return false ;
+ }
+
+ /**
+ * Converts a version string ("1.2.3.4") to an array
+ * Ensures exactly four elements (fills up with "0" if necessary)
+ @return array like [ "1" , "2" , "3" , "4" ]
+ */
+ function makeVersionArray ( $v ) {
+ # Convert to array
+ if ( !is_array ( $v ) ) {
+ $v = explode ( "." , $v ) ;
+ $ret = array () ;
+ foreach ( $v AS $x ) {
+ $x = trim ( $x ) ;
+ if ( !is_numeric ( $x ) )
+ continue ;
+ $ret[] = $x ;
+ }
+ } else {
+ $ret = $v ;
+ }
+
+ # Chop/stretch to length four
+ while ( count ( $ret ) > 4 )
+ array_pop ( $ret ) ;
+ while ( count ( $ret ) < 4 )
+ $ret[] = "0" ;
+ return $ret ;
+ }
+
+ /**
+ * Print an error and die a horrible death!
+ */
+ function error ( $e ) {
+ print $e . "<br/>\n" ;
+ exit ( 1 ) ;
+ }
+
+} # End of class "Upgrader"
+
+$wgUpgrader = new Upgrader ;
+
+
+?>
\ No newline at end of file