User:Dantman/Function Upgrade Hacks

From Meta, a Wikimedia project coordination wiki

These are a few hacks I use for altering MediaWiki to suit some extra purposes. Mostly these are useful for merging multiple MediaWiki installations into a network.

The base reason for the creation of these hacks is to allow certain tables to be moved to a shared area where all your wikis may access them. The full potential of all the hacks together allows for you to Semi-Share permissions between your wikis (This means that some flags such as Staff will be effective on all your wikis, while others like Sysop will only apply to individual wikis) Note that if you want the semi shared permissions route, some extensions such as Makesysop, and Desysop will require them to be hacked to work. I'll try and document as much of that as I can.

Database Upgrade Hack[edit]

This hack upgrades The database functions to allow for some additional functions including:

  • Select Functions can specify the Normal, Shared, or Meta (Kinda like a Central Wiki) databases for different functionality.
  • An option at the end of the Select function allows for the SQL to be exported instead of a query, this allows for Multiple Select statements to be combined with the UNION statement. This is very handy for cases when you further this extension to it's full potential.
  • $wgMetaDB is the database of a Central Wiki, I don't have a definite working use of this, but it was intended for the use of shared MediaWiki messages such as how Wikia has working.
  • Previously Setting $wgSharedDB made the users shared. But now you must set it and set $wgSharedUsers to true to share users.
  • Setting $wgSharedInterwiki to true will access the interwiki table from the shared DB instead of local. This allows you to give all your wikis the same set of interwiki links to ease the process of adding more.
  • $wgSharedTables is an array of tables. These tables will be accessed from the shared database instead of locally. This is handy for making the tables created by extensions available to all your wikis, such as making all polls universal.
  • $wgSharedGroups has multiple uses. Setting this to false is the default, all permissions will apply only on individual wikis. Setting this to true will make all permissions drawn from the shared database. This also has a more advanced function. If you also install the Shared Permissions portion of my hacks and dedicate yourself to making sure your extensions are configured right setting this option to a list of permissions will make those permissions accessible from the shared database while all other permissions will be effective only on the individual wikis. Note that you will manually half to move all permissions of each type to their respective database, or else you will have "phantom permissions" that can't be removed using the interface.

Please note that for things such as sharing the user table or interwiki table you must move them to the shared database on your own. And will most likely need to give the wiki user permission to that shared database. I recommend using one user as the user for all your wikis.

includes/Database.php[edit]

There are 2 functions to upgrade for this file. These were modified from the 1.7.1 code, which is the same as the 1.8.2 code.

The first upgrade is the Select statement. This is the new code:

	/**
	 * SELECT wrapper
	 */
	function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $useDB = LOCAL_DB, $makeQuery = true )
	{
		if( is_array( $vars ) ) {
			$vars = implode( ',', $vars );
		}
		if( !is_array( $options ) ) {
			$options = array( $options );
		}
		if( is_array( $table ) ) {
			if ( @is_array( $options['USE INDEX'] ) )
				$from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] );
			else
				$from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
		} elseif ($table!='') {
			$from = ' FROM ' . $this->tableName( $table, $useDB );
		} else {
			$from = '';
		}

		list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $options );

		if( !empty( $conds ) ) {
			if ( is_array( $conds ) ) {
				$conds = $this->makeList( $conds, LIST_AND );
			}
			$sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $tailOpts";
		} else {
			$sql = "SELECT $startOpts $vars $from $useIndex $tailOpts";
		}

		return $makeQuery ? $this->query( $sql, $fname ) : $sql;
	}

The second upgrade is to the TableName function:

	/**
	 * Format a table name ready for use in constructing an SQL query
	 *
	 * This does two important things: it quotes table names which as necessary,
	 * and it adds a table prefix if there is one.
	 *
	 * All functions of this object which require a table name call this function
	 * themselves. Pass the canonical name to such functions. This is only needed
	 * when calling query() directly.
	 *
	 * @param string $name database table name
	 */
	function tableName( $name, $useDB = LOCAL_DB ) {
		global $wgDBname, $wgSharedDB, $wgMetaDB, $wgSharedUsers, $wgSharedInterwiki, $wgSharedGroups, $wgSharedTables;
		# Skip quoted literals
		if ( $name{0} != '`' ) {
			if ( $this->mTablePrefix !== '' &&  strpos( '.', $name ) === false ) {
				$name = "{$this->mTablePrefix}$name";
			}
			if ( isset( $wgSharedDB ) && ( $useDB == SHARED_DB || (in_array($name, ( is_array( $wgSharedTables ) ? $wgSharedTables : array( $wgSharedTables ) ) ) || "{$this->mTablePrefix}user" == $name || "{$this->mTablePrefix}interwiki" == $name || ( "{$this->mTablePrefix}user_groups" == $name && $wgSharedGroups === true ) ) ) ) ) { $useDB = SHARED_DB; }
			elseif ( isset( $wgMetaDB ) && $useDB == META_DB ) { $useDB = META_DB; }
			else { $useDB = LOCAL_DB; }
			# Standard quoting
			$name = ( ($useDB == SHARED_DB && isset( $wgSharedDB ) ) ? "`$wgSharedDB`." : ( ($useDB == META_DB && isset( $wgMetaDB ) ? "`$wgMetaDB`." : '') ) ) . "`$name`";
		}
		return $name;
	}

includes/Defines.php[edit]

For the defines added for database functions to work we must add them to the Defines.php file these are good in between:

/**#@+
 * Database related constants
 */
define( 'DBO_DEBUG', 1 );
define( 'DBO_NOBUFFER', 2 );
define( 'DBO_IGNORE', 4 );
define( 'DBO_TRX', 8 );
define( 'DBO_DEFAULT', 16 );
define( 'DBO_PERSISTENT', 32 );
/**#@-*/

...and...

/**#@+
 * Virtual namespaces; don't appear in the page database
 */
define('NS_MEDIA', -2);
define('NS_SPECIAL', -1);
/**#@-*/

The code is:

/*Special Database constants used in the $useDB paramater to determine the location of data.*/
define( 'LOCAL_DB', 1 );
define( 'SHARED_DB', 2 );
define( 'META_DB', 3 );

Semi Shared Permissions Hack[edit]

This is the heart of my work, The beauty of this hack allows for you to create a Staff flag and share it among all your wikis, and leave Bureaucrats, and Sysops to individual wikis.

You must first install the Database Upgrade Hack. The information on the semi shared permissions settings is listed inside of the Database Upgrade Hack.

includes/User.php[edit]

The heart of hacking user permissions lies within the User.php file so this is where to begin. There are a few sections we must upgrade for our purposes.

The first upgrade allows the permissions of a user to be seen from 2 different areas. This starts in the loadFromDatabase function: We must change:

			$res = $dbr->select( 'user_groups',
				array( 'ug_group' ),
				array( 'ug_user' => $this->mId ),
				$fname );

Into:

			$res = $dbr->query(
				$dbr->select( 'user_groups',
					array( 'ug_group' ),
					array( 'ug_user' => $this->mId ),
					$fname, array(), LOCAL_DB, false ) .
				" UNION " .
				$dbr->select( 'user_groups',
					array( 'ug_group' ),
					array( 'ug_user' => $this->mId ),
					$fname, array(), SHARED_DB, false ) );

If you paid attention to what we did, you'd see that what we did was use our upgrades to the Database functions to allow us to combine the same query together, but allow the data to be combined from 2 different databases.

Now we upgrade the addGroup and removeGroup functions so that they are done correctly. To spare the details, I'm just going to give you the new function code. Same as before, Modified from 1.7.1, still the same in 1.8.2.

	/**
	 * Add the user to the given group.
	 * This takes immediate effect.
	 * @string $group
	 */
	function addGroup( $group ) {
		global $wgDBname, $wgSharedDB, $wgSharedGroups;
		
		$dbw =& wfGetDB( DB_MASTER );
		if( isset( $wgSharedDB ) && ( ( is_array ( $wgSharedGroups ) && in_array( $group, $wgSharedGroups ) ) || $wgSharedGroups === true ) ) { $useDB = SHARED_DB; }
		else { $useDB = LOCAL_DB; }
		$dbw->insert( 'user_groups',
			array(
				'ug_user'  => $this->getID(),
				'ug_group' => $group,
			),
			'User::addGroup',
			array( 'IGNORE' ),
			$useDB );

		$this->mGroups = array_merge( $this->mGroups, array( $group ) );
		$this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );

		$this->invalidateCache();
		$this->saveSettings();
	}

	/**
	 * Remove the user from the given group.
	 * This takes immediate effect.
	 * @string $group
	 */
	function removeGroup( $group ) {
		global $wgDBname, $wgSharedDB, $wgSharedGroups;
		
		$dbw =& wfGetDB( DB_MASTER );
		if( isset( $wgSharedDB ) && ( ( is_array ( $wgSharedGroups ) && in_array( $group, $wgSharedGroups ) ) || $wgSharedGroups === true ) ) { $useDB = SHARED_DB; }
		else { $useDB = LOCAL_DB; }
		$dbw->delete( 'user_groups',
			array(
				'ug_user'  => $this->getID(),
				'ug_group' => $group,
			),
			'User::removeGroup',
			array(),
			$useDB );

		$this->mGroups = array_diff( $this->mGroups, array( $group ) );
		$this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );

		$this->invalidateCache();
		$this->saveSettings();
	}

includes/SpecialListusers.php[edit]

Ok, please don't skip this part of the upgrade. Using semi-shared permissions has a unfortunate side effect. Unlike other parts of MediaWiki Special:Listusers isn't configured to depend on the code inside of the other files like we would like it to. This means that that special page will be horribly glitched if we don't fix it.

The main function we need to upgrade is the getSQL() function. Again, to make this easy I'll give you the new function. And again, this is modified from 1.7.1, which is the same in 1.8.2:

	function getSQL() {
		global $wgSharedGroups;
		
		$dbr =& wfGetDB( DB_SLAVE );
		$user = $dbr->tableName( 'user' );
		$user_groups = $dbr->tableName( 'user_groups' );
		//Used for trans-table groups
		if( is_array( $wgSharedGroups ) ) {
			$ug_gr_local = $dbr->tableName( 'user_groups', LOCAL_DB );
			$ug_gr_shared = $dbr->tableName( 'user_groups', SHARED_DB );
			
			$g[] = "(SELECT COUNT($ug_gr_local.`ug_group`) FROM $ug_gr_local WHERE user_id=$ug_gr_local.`ug_user`)";
			$g[] = "(SELECT COUNT($ug_gr_shared.`ug_group`) FROM $ug_gr_shared WHERE user_id=$ug_gr_shared.`ug_user`)";
			
			$us_groups = implode(", ", array($user, $ug_gr_local, $ug_gr_shared));
			$ug_group = implode(" + ", $g);
		} else {
			$ug_group = "COUNT($user_groups)";
			$us_groups = implode(", ", array($user, $user_groups));
		}
		
		// We need to get an 'atomic' list of users, so that we
		// don't break the list half-way through a user's group set
		// and so that lists by group will show all group memberships.
		//
		// On MySQL 4.1 we could use GROUP_CONCAT to grab group
		// assignments together with users pretty easily. On other
		// versions, it's not so easy to do it consistently.
		// For now we'll just grab the number of memberships, so
		// we can then do targetted checks on those who are in
		// non-default groups as we go down the list.

		$userspace = NS_USER;
		$sql = "SELECT 'Listusers' as type, $userspace AS namespace, user_name AS title, " .
			"user_name as value, user_id, $ug_group AS numgroups " .
			"FROM $us_groups".
			$this->userQueryWhere( $dbr ) .
			" GROUP BY user_name";

		return $sql;
	}

Fixing Extensions[edit]

As I stated some extensions break with the new system of semi shared permissions. While every function that requests what flags a user has, and uses the add/remove groups function in User.php works perfectly arround the entire MediaWiki system. Unfortunately Special:Makesysop and Special:Desysop for some reason try to do the SQL themselves. This means that they break when they try and do that work manualy instead of depending on the System's internal functions like they should.

includes/SpecialMakesysop_body.php[edit]

Under Construction...