Rework the user_groups system, again, into something that seems to actually
authorBrion Vibber <brion@users.mediawiki.org>
Thu, 9 Jun 2005 09:49:10 +0000 (09:49 +0000)
committerBrion Vibber <brion@users.mediawiki.org>
Thu, 9 Jun 2005 09:49:10 +0000 (09:49 +0000)
more or less work for now.

* user_groups ur_group is now a short string key ('sysop' etc)
* groups table is gone
* user_rights table is gone
* Permissions for groups are for now set in $wgGroupPermissions.
An in-database management system could be re-added in the future
if it's really needed, but for now it's mostly just been screwing
things up.
* Group.php and Special:Groups are deprecated; will probably die.
* User group memberships are set explicitly through addGroup and
removeGroup methods instead of being re-saved on every change to
the user record.

Group keys are migrated from user_rights at upgrade time for older wikis.
The fields in prior 1.5alpha tables were too screwed up and will need to
manually have sysops re-assigned.

The Makesysop extension will need some minor tweaks.

14 files changed:
RELEASE-NOTES
config/index.php
includes/DefaultSettings.php
includes/HTMLForm.php
includes/SpecialListusers.php
includes/SpecialPage.php
includes/SpecialStatistics.php
includes/SpecialUserrights.php
includes/Title.php
includes/User.php
maintenance/archives/patch-user_groups.sql [new file with mode: 0644]
maintenance/parserTests.php
maintenance/tables.sql
maintenance/updaters.inc

index 95a07fc..c60284e 100644 (file)
@@ -272,6 +272,7 @@ Various bugfixes, small features, and a few experimental things:
 * (bug 2309) Allow templates and template parameters in HTML attribute zone,
   with proper validation checks. (regression from fix for 2304)
 * Disallow close tags and enforce empty tags for <hr> and <br>
+* Changed user_groups format quite a bit.
 
 
 === Caveats ===
index 8d9223e..9600f00 100644 (file)
@@ -565,7 +565,6 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) {
                        print "<li>Creating tables...";
                        dbsource( "../maintenance/tables.sql", $wgDatabase );
                        dbsource( "../maintenance/interwiki.sql", $wgDatabase );
-                       dbsource( "../maintenance/archives/patch-userlevels-defaultgroups.sql", $wgDatabase );
                        print " done.</li>\n";
 
                        print "<li>Initializing data...";
@@ -585,9 +584,10 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) {
                                if ( 0 == $u->idForName() ) {
                                        $u->addToDatabase();
                                        $u->setPassword( $conf->getSysopPass() );
-                                       $u->addRight( "sysop" );
-                                       $u->addRight( "bureaucrat" );
                                        $u->saveSettings();
+
+                                       $u->addGroup( "sysop" );
+                                       $u->addGroup( "bureaucrat" );
                                        
                                        print "<li>Created sysop account <tt>" .
                                                htmlspecialchars( $conf->SysopName ) . "</tt>.</li>\n";
index ffb26c1..4e79cea 100644 (file)
@@ -671,12 +671,25 @@ $wgBlockExpiryOptions = "2 hours,1 day,3 days,1 week,2 weeks,1 month,3 months,6
 $wgAutoblockExpiry             = 86400; # Number of seconds before autoblock entries expire
 
 /**
- * Static user groups serialized record
- * To avoid database access, you can set this to a user groups record as returned 
- * by Special:Groups with the magic parameter showrecord=1. This will however mean 
- * that you won't be able to edit them at runtime.
- */
-$wgStaticGroups = false;
+ * Permission keys given to users in each group.
+ * All users are implicitly in the '*' group including anonymous visitors;
+ * logged-in users are all implicitly in the 'user' group. These will be
+ * combined with the permissions of all groups that a given user is listed
+ * in in the user_groups table.
+ */
+$wgGroupPermissions = array(
+       '*'          => array( 'read', 'createaccount' ),
+       'user'       => array( 'read', 'move' ),
+       
+       'bot'        => array( 'bot' ),
+       'sysop'      => array( 'createaccount', 'patrol', 'protect', 'delete',
+                              'rollback', 'block', 'editinterface' ),
+       'bureaucrat' => array( 'userrights' ),
+       'steward'    => array( 'makesysop' ), # technically this is for an extension...
+       'developer'  => array( 'siteadmin' ),
+);
+
+
 
 # Proxy scanner settings
 #
@@ -1319,14 +1332,6 @@ $wgBrowserBlackList = array(
 # $wgLocaltimezone = 'CET';
 $wgLocaltimezone = null;
 
-/**
- * User level management
- * The number is the database id of a group you want users to be attached by
- * default. A better interface should be coded [av]
- */
-$wgAnonGroupId = 1;
-$wgLoggedInGroupId = 2;
-
 
 /**
  * When translating messages with wfMsg(), it is not always clear what should be
index b15e70d..12147d5 100644 (file)
@@ -120,26 +120,34 @@ class HTMLForm {
  * @param boolean $reverse If true, multiple select will hide selected elements (default false).
 */
 function HTMLSelectGroups($selectname, $selectmsg, $selected=array(), $multiple=false, $size=6, $reverse=false) {
-       global $wgOut;
-       $groups =& Group::getAllGroups();
+       $groups = User::getAllGroups();
+       $out = htmlspecialchars( wfMsg( $selectmsg ) );
        
-       $out = wfMsg($selectmsg);
-       $out .= '<select name="'.$selectname;
-       if($multiple) { $out.='[]" multiple="multiple" size="'.$size; }
-       $out.= "\">\n";
+       if( $multiple ) {
+               $attribs = array(
+                       'name'    => $selectname . '[]',
+                       'multiple'=> 'multiple',
+                       'size'    => $size );
+       } else {
+               $attribs = array( 'name' => $selectname );
+       }
+       $out .= wfElement( 'select', $attribs, null );
        
-       foreach ( $groups as $id => $g ) {
-               if($multiple) {
+       foreach( $groups as $group ) {
+               $attribs = array( 'value' => $group );
+               if( $multiple ) {
                        // for multiple will only show the things we want
-                       if(in_array($id, $selected) xor $reverse) { 
-                               $out .= '<option value="'.$id.'">'.$wgOut->parse( $g->getExpandedName() )."</option>\n";
+                       if( !in_array( $group, $selected ) xor $reverse ) {
+                               continue;
                        }
                } else {
-                       $out .= '<option ';
-                       if(in_array($id, $selected)) { $out .= 'selected="selected" '; }
-                       $out .= 'value="'.$id.'">'.$wgOut->parse( $g->getExpandedName() )."</option>\n";
+                       if( in_array( $group, $selected ) ) {
+                               $attribs['selected'] = 'selected';
+                       }
                }
+               $out .= wfElement( 'option', $attribs, User::getGroupName( $group ) ) . "\n";
        }
+
        $out .= "</select>\n";
        return $out;
 }
index 28837db..e30078a 100644 (file)
@@ -64,18 +64,22 @@ class ListUsersPage extends QueryPage {
                // form header
                $out = '<form method="get" action="'.$action.'">' .
                                '<input type="hidden" name="title" value="'.$special.'" />' .
-                               wfMsg( 'grouplevels-editgroup-name' ) . '<select name="group">';
+                               wfMsg( 'groups-editgroup-name' ) . '<select name="group">';
 
                // get all group names and IDs
-               $groups =& Group::getAllGroups();
+               $groups = User::getAllGroups();
                
                // we want a default empty group
                $out.= '<option value=""></option>';
                
                // build the dropdown list menu using datas from the database
                foreach ( $groups as $group ) {
-                       $selected = ($group->getId() == $this->requestedGroup) ? ' selected ' : '' ;
-                       $out.= '<option value="'.$group->getId().'" '.$selected.'>'.$group->getExpandedName().'</option>';
+                       $selected = ($group == $this->requestedGroup);
+                       $out .= wfElement( 'option',
+                               array_merge(
+                                       array( 'value' => $group ),
+                                       $selected ? array( 'selected' => 'selected' ) : array() ),
+                               User::getGroupName( $group ) );
                }
                $out .= '</select> ';
 
@@ -89,24 +93,16 @@ class ListUsersPage extends QueryPage {
        
        function getSQL() {
                $dbr =& wfGetDB( DB_SLAVE );
-       /* system showing possible actions for users
-               $user = $dbr->tableName( 'user' );
-               $user_rights = $dbr->tableName( 'user_rights' );
-               $userspace = Namespace::getUser();
-               return "SELECT ur_rights as type, $userspace as namespace, user_name as title, " .
-                       "user_name as value FROM $user LEFT JOIN $user_rights ON user_id = ur_user";
-       */
-       /** Show groups instead */
                $user = $dbr->tableName( 'user' );
                $user_groups = $dbr->tableName( 'user_groups' );
                
                $userspace = NS_USER;
-               $sql = "SELECT CONCAT('Listusers ', ug_group) as type, $userspace AS namespace, user_name AS title, user_name as value " .
+               $sql = "SELECT 'Listusers' as type, $userspace AS namespace, user_name AS title, ug_group as value " .
                        "FROM $user ".
                        "LEFT JOIN $user_groups ON user_id =ug_user ";
 
                if($this->requestedGroup != '') {
-                       $sql .=  "WHERE ug_group = '" . IntVal( $this->requestedGroup ) . "' ";
+                       $sql .=  'WHERE ug_group = ' . $dbr->addQuotes( $this->requestedGroup ) . ' ';
                        if($this->requestedUser != '') {
                                $sql .= "AND user_name = " . $dbr->addQuotes( $this->requestedUser ) . ' ';
                        }
@@ -138,10 +134,7 @@ class ListUsersPage extends QueryPage {
        function clearGroups() {
                $this->concatGroups = '';       
        }
-/*
-       var $previousResult = false;
-       var $concatGroups = '';
-*/
+       
        function formatResult( $skin, $result ) {
                global $wgContLang;
                $name = false;
@@ -155,9 +148,9 @@ class ListUsersPage extends QueryPage {
                }
 
                if( is_object( $result ) && $result->type != '') {
-                       $group = Group::newFromId( intval( strstr( $result->type, ' ' ) ) );
+                       $group = $result->value;
                        if ( $group ) {
-                               $groupName = $group->getExpandedName();
+                               $groupName = User::getGroupName( $group );
                                $this->appendGroups( $skin->makeLink( wfMsgForContent( 'administrators' ), $groupName ) );
                        }
                }
index 5469001..4da06a8 100644 (file)
@@ -72,7 +72,7 @@ $wgSpecialPages = array(
        'Lockdb'                => new SpecialPage( 'Lockdb', 'siteadmin' ),
        'Unlockdb'              => new SpecialPage( 'Unlockdb', 'siteadmin' ),
        'Userrights'    => new SpecialPage( 'Userrights', 'userrights' ),
-       'Groups'                => new SpecialPage( 'Groups' ),
+       // 'Groups'             => new SpecialPage( 'Groups' ), # currently borken
 );
 
 global $wgUseValidation ;
index ba4d678..bb17934 100644 (file)
@@ -13,7 +13,7 @@ function wfSpecialStatistics() {
        $fname = 'wfSpecialStatistics';
 
        $dbr =& wfGetDB( DB_SLAVE );
-       extract( $dbr->tableNames( 'page', 'site_stats', 'user', 'user_rights' ) );
+       extract( $dbr->tableNames( 'page', 'site_stats', 'user', 'user_groups' ) );
 
        $sql = "SELECT COUNT(page_namespace) AS total FROM $page";
        $res = $dbr->query( $sql, $fname );
@@ -44,7 +44,7 @@ function wfSpecialStatistics() {
        $row = $dbr->fetchObject( $res );
        $total = $row->total;
 
-       $sql = "SELECT COUNT(ur_user) AS total FROM $user_rights WHERE ur_rights LIKE '%sysop%'";
+       $sql = "SELECT COUNT(*) AS total FROM $user_groups WHERE ug_group='sysop'";
        $res = $dbr->query( $sql, $fname );
        $row = $dbr->fetchObject( $res );
        $admins = $row->total;
index 6903deb..66c6ef1 100644 (file)
@@ -10,7 +10,6 @@
 
 /** */
 require_once('HTMLForm.php');
-require_once('Group.php');
 
 /** Entry point */
 function wfSpecialUserrights() {
@@ -73,12 +72,12 @@ class UserrightsForm extends HTMLForm {
                $u = User::newFromName($username);
 
                if(is_null($u)) {
-                       $wgOut->addHTML('<p>'.wfMsg('nosuchusershort',$username).'</p>');
+                       $wgOut->addWikiText( wfMsg( 'nosuchusershort', htmlspecialchars( $username ) ) );
                        return;
                }
 
                if($u->getID() == 0) {
-                       $wgOut->addHTML('<p>'.wfMsg('nosuchusershort',$username).'</p>');
+                       $wgOut->addWikiText( wfMsg( 'nosuchusershort', htmlspecialchars( $username ) ) );
                        return;
                }               
 
@@ -93,10 +92,17 @@ class UserrightsForm extends HTMLForm {
                        $newGroups = array_merge($newGroups, $addgroup);
                }
                $newGroups = array_unique( $newGroups );
+               
+               wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
+               wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
 
                // save groups in user object and database
-               $u->setGroups($newGroups);
-               $u->saveSettings();
+               foreach( $removegroup as $group ) {
+                       $u->removeGroup( $group );
+               }
+               foreach( $addgroup as $group ) {
+                       $u->addGroup( $group );
+               }
 
                $log = new LogPage( 'rights' );
                $log->addEntry( 'rights', Title::makeTitle( NS_USER, $u->getName() ), '', array( $this->makeGroupNameList( $oldGroups ),
@@ -104,15 +110,7 @@ class UserrightsForm extends HTMLForm {
        }
 
        function makeGroupNameList( $ids ) {
-               $s = '';
-               foreach( $ids as $id ) {
-                       if ( $s != '' ) {
-                               $s .= ', ';
-                       }
-                       $groupObj = Group::newFromId( $id );
-                       $s .= $groupObj->getExpandedName();
-               }
-               return $s;
+               return implode( ', ', $ids );
        }
 
        /**
@@ -126,7 +124,10 @@ class UserrightsForm extends HTMLForm {
                $wgOut->addHTML( "<form name=\"uluser\" action=\"$this->action\" method=\"post\">\n" );
                $wgOut->addHTML( $this->fieldset( 'lookup-user',
                                $this->textbox( 'user-editname' ) .
-                               '<input type="submit" name="ssearchuser" value="'.wfMsg('editusergroup').'" />'
+                               wfElement( 'input', array(
+                                       'type'  => 'submit',
+                                       'name'  => 'ssearchuser',
+                                       'value' => wfMsg( 'editusergroup' ) ) )
                ));
                $wgOut->addHTML( "</form>\n" );
        }
@@ -139,30 +140,30 @@ class UserrightsForm extends HTMLForm {
                global $wgOut;
                
                $user = User::newFromName($username);
-               $encUser = htmlspecialchars( $username );
-               if(is_null($user)) {
-                       $wgOut->addHTML('<p>'.wfMsg('nosuchusershort', $encUser).'</p>');
+               if( is_null( $user ) || $user->getID() == 0 ) {
+                       $wgOut->addWikiText( wfMsg( 'nosuchusershort', wfEscapeWikiText( $username ) ) );
                        return;
                }
-
-               if($user->getID() == 0) {
-                       $wgOut->addHTML('<p>'.wfMsg('nosuchusershort', $encUser).'</p>');
-                       return;
-               }               
                
                $groups = $user->getGroups();
 
                $wgOut->addHTML( "<form name=\"editGroup\" action=\"$this->action\" method=\"post\">\n".
-                                                '<input type="hidden" name="user-editname" value="'.$encUser.'" />');
-               $wgOut->addHTML( $this->fieldset( 'editusergroup',
-                       wfMsg('editing', $this->mRequest->getVal('user-editname')).".<br />\n" .
+                       wfElement( 'input', array(
+                               'type'  => 'hidden',
+                               'name'  => 'user-editname',
+                               'value' => $username ) ) .
+                       $this->fieldset( 'editusergroup',
+                       $wgOut->parse( wfMsg('editing', $username ) ) .
                        '<table border="0" align="center"><tr><td>'.
                        HTMLSelectGroups('member', $this->mName.'-groupsmember', $groups,true,6).
                        '</td><td>'.
                        HTMLSelectGroups('available', $this->mName.'-groupsavailable', $groups,true,6,true).
                        '</td></tr></table>'."\n".
-                       '<p>'.wfMsg('userrights-groupshelp').'</p>'."\n".
-                       '<input type="submit" name="saveusergroups" value="'.wfMsg('saveusergroups').'" />'
+                       $wgOut->parse( wfMsg('userrights-groupshelp') ) .
+                       wfElement( 'input', array(
+                               'type'  => 'submit',
+                               'name'  => 'saveusergroups',
+                               'value' => wfMsg( 'saveusergroups' ) ) )
                        ));
                $wgOut->addHTML( "</form>\n" );
        }
index 0bc30af..60fc458 100644 (file)
@@ -958,7 +958,7 @@ class Title {
                        /** If anon users can create an account,
                            they need to reach the login page first! */
                        if( $wgUser->isAllowed( 'createaccount' )
-                           && $this->mId == NS_SPECIAL
+                           && $this->getNamespace() == NS_SPECIAL
                            && $this->getText() == 'Userlogin' ) {
                                return true;
                        }
index f029cbc..1038801 100644 (file)
@@ -9,7 +9,6 @@
  *
  */
 require_once( 'WatchedItem.php' );
-require_once( 'Group.php' );
 
 # Number of characters in user_token field
 define( 'USER_TOKEN_LENGTH', 32 );
@@ -32,9 +31,7 @@ class User {
        var $mToken;
        var $mRealName;
        var $mHash;
-       /** Array of group id the user belong to */
        var $mGroups;
-       /**#@-*/
 
        /** Construct using User:loadDefaults() */
        function User() {
@@ -563,7 +560,7 @@ class User {
         * Load a user from the database
         */
        function loadFromDatabase() {
-               global $wgCommandLineMode, $wgAnonGroupId, $wgLoggedInGroupId;
+               global $wgCommandLineMode;
                $fname = "User::loadFromDatabase";
                
                # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress 
@@ -577,15 +574,9 @@ class User {
                $this->mId = IntVal( $this->mId );
 
                /** Anonymous user */
-               if(!$this->mId) {
+               if( !$this->mId ) {
                        /** Get rights */
-                       $anong = Group::newFromId($wgAnonGroupId);
-                       if (!$anong) 
-                               wfDebugDieBacktrace("Please update your database schema "
-                                       ."and populate initial group data from "
-                                       ."maintenance/archives patches");
-                       $anong->loadFromDatabase();
-                       $this->mRights = explode(',', $anong->getRights());
+                       $this->mRights = $this->getGroupPermissions( array( '*' ) );
                        $this->mDataLoaded = true;
                        return;
                } # the following stuff is for non-anonymous users only
@@ -607,31 +598,16 @@ class User {
                        $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
                        $this->mToken = $s->user_token;
 
-                       // Get groups id
-                       $res = $dbr->select( 'user_groups', array( 'ug_group' ), array( 'ug_user' => $this->mId ) );
-                       
-                       // add the default group for logged in user
-                       $this->mGroups = array( $wgLoggedInGroupId );
-
-                       while($group = $dbr->fetchRow($res)) {
-                               if ( $group[0] != $wgLoggedInGroupId ) {
-                                       $this->mGroups[] = $group[0];
-                               }
-                       }
-
-
-                       $this->mRights = array();
-                       // now we merge groups rights to get this user rights
-                       foreach($this->mGroups as $aGroupId) {
-                               $g = Group::newFromId($aGroupId);
-                               $g->loadFromDatabase();
-                               $this->mRights = array_merge($this->mRights, explode(',', $g->getRights()));
+                       $res = $dbr->select( 'user_groups',
+                               array( 'ug_group' ),
+                               array( 'ug_user' => $this->mId ),
+                               $fname );
+                       $this->mGroups = array();
+                       while( $row = $dbr->fetchObject( $res ) ) {
+                               $this->mGroups[] = $row->ug_group;
                        }
-                       
-                       // array merge duplicate rights which are part of several groups
-                       $this->mRights = array_unique($this->mRights);
-                       
-                       $dbr->freeResult($res);
+                       $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups );
+                       $this->mRights = $this->getGroupPermissions( $effectiveGroups );
                }
 
                $this->mDataLoaded = true;
@@ -830,24 +806,75 @@ class User {
                $this->loadFromDatabase();
                return $this->mRights;
        }
-       
-       function addRight( $rname )     {
-               $this->loadFromDatabase();
-               array_push( $this->mRights, $rname );
-               $this->invalidateCache();
-       }
 
+       /**
+        * Get the list of explicit group memberships this user has.
+        * The implicit * and user groups are not included.
+        * @return array of strings
+        */
        function getGroups() {
                $this->loadFromDatabase();
                return $this->mGroups;
        }
 
-       function setGroups($groups) {
-               $this->loadFromDatabase();
-               $this->mGroups = $groups;
+       /**
+        * Get the list of implicit group memberships this user has.
+        * This includes all explicit groups, plus 'user' if logged in
+        * and '*' for all accounts.
+        * @return array of strings
+        */
+       function getEffectiveGroups() {
+               $base = array( '*' );
+               if( $this->isLoggedIn() ) {
+                       $base[] = 'user';
+               }
+               return array_merge( $base, $this->getGroups() );
+       }
+       
+       /**
+        * Remove the user from the given group.
+        * This takes immediate effect.
+        * @string $group
+        */
+       function addGroup( $group ) {
+               $dbw =& wfGetDB( DB_MASTER );
+               $dbw->insert( 'user_groups',
+                       array(
+                               'ug_user'  => $this->getID(),
+                               'ug_group' => $group,
+                       ),
+                       'User::addGroup',
+                       array( 'IGNORE' ) );
+               
+               $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 ) {
+               $dbw =& wfGetDB( DB_MASTER );
+               $dbw->delete( 'user_groups',
+                       array(
+                               'ug_user'  => $this->getID(),
+                               'ug_group' => $group,
+                       ),
+                       'User::removeGroup' );
+               
+               $this->mGroups = array_diff( $this->mGroups, array( $group ) );
+               $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
+               
+               $this->invalidateCache();
+               $this->saveSettings();
        }
 
+
        /**
         * A more legible check for non-anonymousness.
         * Returns true if the user is not an anonymous visitor.
@@ -1167,23 +1194,7 @@ class User {
                                'user_id' => $this->mId
                        ), $fname
                );
-               $dbw->set( 'user_rights', 'ur_rights', implode( ',', $this->mRights ),
-                       'ur_user='. $this->mId, $fname ); 
                $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
-               
-               // delete old groups
-               $dbw->delete( 'user_groups', array( 'ug_user' => $this->mId), $fname);
-               
-               // save new ones
-               foreach ($this->mGroups as $group) {
-                       $dbw->replace( 'user_groups',
-                               array(array('ug_user','ug_group')),
-                               array(
-                                       'ug_user' => $this->mId,
-                                       'ug_group' => $group
-                               ), $fname
-                       );
-               }
        }
 
        
@@ -1226,21 +1237,6 @@ class User {
                        ), $fname
                );
                $this->mId = $dbw->insertId();
-               $dbw->insert( 'user_rights',
-                       array(
-                               'ur_user' => $this->mId,
-                               'ur_rights' => implode( ',', $this->mRights )
-                       ), $fname
-               );
-               
-               foreach ($this->mGroups as $group) {
-                       $dbw->insert( 'user_groups',
-                               array(
-                                       'ug_user' => $this->mId,
-                                       'ug_group' => $group
-                               ), $fname
-                       );
-               }
        }
 
        function spreadBlock() {
@@ -1591,6 +1587,51 @@ class User {
                        return false;
                return true;
        }
+       
+       /**
+        * @param array $groups list of groups
+        * @return array list of permission key names for given groups combined
+        * @static
+        */
+       function getGroupPermissions( $groups ) {
+               global $wgGroupPermissions;
+               $rights = array();
+               foreach( $groups as $group ) {
+                       if( isset( $wgGroupPermissions[$group] ) ) {
+                               $rights = array_merge( $rights, $wgGroupPermissions[$group] );
+                       }
+               }
+               return $rights;
+       }
+
+       /**
+        * @param string $group key name
+        * @return string localized descriptive name, if provided
+        * @static
+        */
+       function getGroupName( $group ) {
+               $key = "group-$group-name";
+               $name = wfMsg( $key );
+               if( $name == '' || $name == "&lt;$key&gt;" ) {
+                       return $group;
+               } else {
+                       return $name;
+               }
+       }
+       
+       /**
+        * Return the set of defined explicit groups.
+        * The * and 'user' groups are not included.
+        * @return array
+        * @static
+        */
+       function getAllGroups() {
+               global $wgGroupPermissions;
+               return array_diff(
+                       array_keys( $wgGroupPermissions ),
+                       array( '*', 'user' ) );
+       }
+
 }
 
 ?>
diff --git a/maintenance/archives/patch-user_groups.sql b/maintenance/archives/patch-user_groups.sql
new file mode 100644 (file)
index 0000000..50f9999
--- /dev/null
@@ -0,0 +1,25 @@
+--
+-- User permissions have been broken out to a separate table;
+-- this allows sites with a shared user table to have different
+-- permissions assigned to a user in each project.
+--
+-- This table replaces the old user_rights field which used a
+-- comma-separated blob.
+--
+CREATE TABLE /*$wgDBprefix*/user_groups (
+  -- Key to user_id
+  ug_user int(5) unsigned NOT NULL default '0',
+  
+  -- Group names are short symbolic string keys.
+  -- The set of group names is open-ended, though in practice
+  -- only some predefined ones are likely to be used.
+  --
+  -- At runtime $wgGroupPermissions will associate group keys
+  -- with particular permissions. A user will have the combined
+  -- permissions of any group they're explicitly in, plus
+  -- the implicit '*' and 'user' groups.
+  ug_group char(16) NOT NULL default '',
+  
+  PRIMARY KEY (ug_user,ug_group),
+  KEY (ug_group)
+) TYPE=InnoDB;
index e18cb72..adb341b 100644 (file)
@@ -327,7 +327,7 @@ class ParserTest {
                        'recentchanges',
                        'watchlist', 'math', 'searchindex',
                        'interwiki', 'querycache',
-                       'objectcache', 'groups'
+                       'objectcache'
                );
        }
        
@@ -406,13 +406,6 @@ class ParserTest {
                                       'iw_local'  => 1 ),
                                ) );
 
-                       # Hack: initialize a group
-                       $db->insert( 'groups', array(
-                               'gr_id' => 1,
-                               'gr_name' => 'Anonymous',
-                               'gr_description' => 'Anonymous users',
-                               'gr_rights' => 'read' ) );
-                       
                        # Hack: Insert an image to work with
                        $db->insert( 'image', array(
                                'img_name'        => 'Foobar.jpg',
index 37db68b..720dc5e 100644 (file)
@@ -114,17 +114,25 @@ CREATE TABLE /*$wgDBprefix*/user (
 -- this allows sites with a shared user table to have different
 -- permissions assigned to a user in each project.
 --
--- TODO: de-blob this; it should be a property table
+-- This table replaces the old user_rights field which used a
+-- comma-separated blob.
 --
-CREATE TABLE /*$wgDBprefix*/user_rights (
+CREATE TABLE /*$wgDBprefix*/user_groups (
   -- Key to user_id
-  ur_user int(5) unsigned NOT NULL,
-  
-  -- Comma-separated list of permission keys
-  ur_rights tinyblob NOT NULL default '',
+  ug_user int(5) unsigned NOT NULL default '0',
   
-  UNIQUE KEY ur_user (ur_user)
-
+  -- Group names are short symbolic string keys.
+  -- The set of group names is open-ended, though in practice
+  -- only some predefined ones are likely to be used.
+  --
+  -- At runtime $wgGroupPermissions will associate group keys
+  -- with particular permissions. A user will have the combined
+  -- permissions of any group they're explicitly in, plus
+  -- the implicit '*' and 'user' groups.
+  ug_group char(16) NOT NULL default '',
+  
+  PRIMARY KEY (ug_user,ug_group),
+  KEY (ug_group)
 ) TYPE=InnoDB;
 
 -- The following table is no longer needed with Enotif >= 2.00
@@ -795,19 +803,11 @@ CREATE TABLE /*$wgDBprefix*/logging (
 
 
 -- Hold group name and description
-CREATE TABLE /*$wgDBprefix*/groups (
-  gr_id int(5) unsigned NOT NULL auto_increment,
-  gr_name varchar(50) NOT NULL default '',
-  gr_description varchar(255) NOT NULL default '',
-  gr_rights tinyblob,
-  PRIMARY KEY  (gr_id)
-
-) TYPE=InnoDB;
-
--- Relation table between user and groups
-CREATE TABLE /*$wgDBprefix*/user_groups (
-       ug_user int(5) unsigned NOT NULL default '0',
-       ug_group int(5) unsigned NOT NULL default '0',
-       PRIMARY KEY  (ug_user,ug_group)
-
-) TYPE=InnoDB;
+--CREATE TABLE /*$wgDBprefix*/groups (
+--  gr_id int(5) unsigned NOT NULL auto_increment,
+--  gr_name varchar(50) NOT NULL default '',
+--  gr_description varchar(255) NOT NULL default '',
+--  gr_rights tinyblob,
+--  PRIMARY KEY  (gr_id)
+--
+--) TYPE=InnoDB;
index 735a724..e17cb5c 100644 (file)
@@ -12,7 +12,7 @@ require_once 'userDupes.inc';
 
 $wgRenamedTables = array(
 #           from             to                  patch file
-       array( 'group',         'groups',           'patch-rename-group.sql' ),
+#      array( 'group',         'groups',           'patch-rename-group.sql' ),
 );
 
 $wgNewTables = array(
@@ -22,8 +22,6 @@ $wgNewTables = array(
        array( 'objectcache',   'patch-objectcache.sql' ),
        array( 'categorylinks', 'patch-categorylinks.sql' ),
        array( 'logging',       'patch-logging.sql' ),
-       array( 'user_rights',   'patch-user_rights.sql' ),
-       array( 'groups',        'patch-userlevels.sql' ),
        array( 'validate',      'patch-validate.sql' ),
 );
 
@@ -38,8 +36,6 @@ $wgNewFields = array(
        array( 'user',          'user_real_name',   'patch-user-realname.sql' ),
        array( 'user',          'user_token',       'patch-user_token.sql' ),
        array( 'user',          'user_email_token', 'patch-user_email_token.sql' ),
-       array( 'user_rights',   'ur_user',          'patch-rename-user_groups-and_rights.sql' ),
-       array( 'groups',        'gr_rights',        'patch-userlevels-rights.sql' ),
        array( 'logging',       'log_params',       'patch-log_params.sql' ),
        array( 'archive',       'ar_rev_id',        'patch-archive-rev_id.sql' ),
        array( 'archive',       'ar_text_id',       'patch-archive-text_id.sql' ),
@@ -238,20 +234,6 @@ function do_user_update() {
        }
 }
 
-# Assumes that the groups table has been added.
-function do_group_update() {
-       global $wgDatabase;
-       $res = $wgDatabase->safeQuery( 'SELECT COUNT(*) AS c FROM !',
-               $wgDatabase->tableName( 'groups' ) );
-       $row = $wgDatabase->fetchObject( $res );
-       $wgDatabase->freeResult( $res );
-       if( $row->c == 0 ) {
-               echo "Adding default group definitions... ";
-               dbsource( "maintenance/archives/patch-userlevels-defaultgroups.sql", $wgDatabase );
-               echo "ok\n";
-       }
-}
-
 /**
  * 1.4 betas were missing the 'binary' marker from logging.log_title,
  * which causes a collation mismatch error on joins in MySQL 4.1.
@@ -572,6 +554,81 @@ function do_user_unique_update() {
        }
 }
 
+function do_user_groups_update() {
+       $fname = 'do_user_groups_update';
+       global $wgDatabase;
+       
+       if( $wgDatabase->tableExists( 'user_groups' ) ) {
+               echo "...user_groups table already exists.\n";
+               return do_user_groups_reformat();
+       }
+       
+       echo "Adding user_groups table... ";
+       dbsource( 'maintenance/archives/patch-user_groups.sql', $wgDatabase );
+       echo "ok\n";
+       
+       if( !$wgDatabase->tableExists( 'user_rights' ) ) {
+               if( $wgDatabase->fieldExists( 'user', 'user_rights' ) ) {
+                       echo "Upgrading from a 1.3 or older database? Breaking out user_rights for conversion...";
+                       dbsource( 'maintenance/archives/patch-user_rights.sql', $wgDatabase );
+                       echo "ok\n";
+               } else {
+                       echo "*** WARNING: couldn't locate user_rights table or field for upgrade.\n";
+                       echo "*** You may need to manually configure some sysops by manipulating\n";
+                       echo "*** the user_groups table.\n";
+                       return;
+               }
+       }
+       
+       echo "Converting user_rights table to user_groups... ";
+       $result = $wgDatabase->select( 'user_rights',
+               array( 'ur_user', 'ur_rights' ),
+               array( "ur_rights != ''" ),
+               $fname );
+       
+       while( $row = $wgDatabase->fetchObject( $result ) ) {
+               $groups = array_unique(
+                       array_map( 'trim',
+                               explode( ',', $row->ur_rights ) ) );
+               
+               foreach( $groups as $group ) {
+                       $wgDatabase->insert( 'user_groups',
+                               array(
+                                       'ug_user'  => $row->ur_user,
+                                       'ug_group' => $group ),
+                               $fname );
+               }
+       }
+       $wgDatabase->freeResult( $result );
+       echo "ok\n";
+}
+
+function do_user_groups_reformat() {
+       # Check for bogus formats from previous 1.5 alpha code.
+       global $wgDatabase;
+       $info = $wgDatabase->fieldInfo( 'user_groups', 'ug_group' );
+       
+       if( $info->type == 'int' ) {
+               $oldug = $wgDatabase->tableName( 'user_groups' );
+               $newug = $wgDatabase->tableName( 'user_groups_bogus' );
+               echo "user_groups is in bogus intermediate format. Renaming to $newug... ";
+               $wgDatabase->query( "ALTER TABLE $oldug RENAME TO $newug" );
+               echo "ok\n";
+               
+               echo "Re-adding fresh user_groups table... ";
+               dbsource( 'maintenance/archives/patch-user_groups.sql', $wgDatabase );
+               echo "ok\n";
+               
+               echo "***\n";
+               echo "*** WARNING: You will need to manually fix up user permissions in the user_groups\n";
+               echo "*** table. Old 1.5 alpha versions did some pretty funky stuff...\n";
+               echo "***\n";
+       } else {
+               echo "...user_groups is in current format.\n";
+       }
+       
+}
+
 function do_all_updates() {
        global $wgNewTables, $wgNewFields, $wgRenamedTables;
        
@@ -592,9 +649,6 @@ function do_all_updates() {
                flush();
        }
        
-       # Add default group data
-       do_group_update(); flush();
-
        # Do schema updates which require special handling
        do_interwiki_update(); flush();
        do_index_update(); flush();
@@ -615,6 +669,7 @@ function do_all_updates() {
        do_drop_img_type(); flush();
        
        do_user_unique_update(); flush();
+       do_user_groups_update(); flush();
        
        initialiseMessages(); flush();
 }