Refactor some of the horror that was Special:Userrights...
authorBrion Vibber <brion@users.mediawiki.org>
Thu, 27 Dec 2007 02:31:58 +0000 (02:31 +0000)
committerBrion Vibber <brion@users.mediawiki.org>
Thu, 27 Dec 2007 02:31:58 +0000 (02:31 +0000)
Split out the remote accessors into a UserRightsProxy object, so the main interface code
doesn't have to worry about it -- it just uses the regular User object interface functions,
without caring that it's got a remote object here.
Should have cleaner behavior with numeric ID accesses as well; it now does a name lookup even for remote ones, and logs with that.

includes/AutoLoader.php
includes/SpecialUserrights.php
includes/UserRightsProxy.php [new file with mode: 0644]

index 4f92f65..96fcce0 100644 (file)
@@ -247,6 +247,7 @@ function __autoload($className) {
                'StringUtils' => 'includes/StringUtils.php',
                'Title' => 'includes/Title.php',
                'User' => 'includes/User.php',
+               'UserRightsProxy' => 'includes/UserRightsProxy.php',
                'MailAddress' => 'includes/UserMailer.php',
                'EmailNotification' => 'includes/UserMailer.php',
                'UserMailer' => 'includes/UserMailer.php',
index d10ece3..a2d78c7 100644 (file)
@@ -90,191 +90,130 @@ class UserrightsForm extends HTMLForm {
         *
         */
        function saveUserGroups( $username, $removegroup, $addgroup, $reason = '') {
-               $split = $this->splitUsername( $username );
-               if( !$split ) {
+               $user = $this->fetchUser( $username );
+               if( !$user ) {
                        return;
                }
-               list( $database, $name ) = $split;
 
-               $oldGroups = $this->getUserGroups();
+               $oldGroups = $user->getGroups();
                $newGroups = $oldGroups;
                // remove then add groups
                if(isset($removegroup)) {
                        $newGroups = array_diff($newGroups, $removegroup);
                        foreach( $removegroup as $group ) {
-                               $this->removeUserGroup( $group );
+                               $user->removeGroup( $group );
                        }
                }
                if(isset($addgroup)) {
                        $newGroups = array_merge($newGroups, $addgroup);
                        foreach( $addgroup as $group ) {
-                               $this->addUserGroup( $group );
+                               $user->addGroup( $group );
                        }
                }
                $newGroups = array_unique( $newGroups );
 
                // Ensure that caches are cleared
-               $this->touchUser( $database );
+               $user->invalidateCache();
 
                wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
                wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
-               wfRunHooks( 'UserRights', array( &$u, $addgroup, $removegroup ) );
+               if( $user instanceof User ) {
+                       // hmmm
+                       wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) );
+               }
 
                $log = new LogPage( 'rights' );
-               $log->addEntry( 'rights', Title::makeTitleSafe( NS_USER, $username ), $this->mReason, array( $this->makeGroupNameList( $oldGroups ),
-                       $this->makeGroupNameList( $newGroups ) ) );
+               $log->addEntry( 'rights',
+                       $user->getUserPage(),
+                       $this->mReason,
+                       array(
+                               $this->makeGroupNameList( $oldGroups ),
+                               $this->makeGroupNameList( $newGroups ) ) );
        }
 
        /**
         * Edit user groups membership
         * @param string $username Name of the user.
         */
-       function editUserGroupsForm($username) {
+       function editUserGroupsForm( $username ) {
                global $wgOut;
 
-               $split = $this->splitUsername( $username );
-               if( !$split ) {
+               $user = $this->fetchUser( $username );
+               if( !$user ) {
                        return;
                }
-               list( $database, $name ) = $split;
 
-               $groups = $this->getUserGroups();
+               $groups = $user->getGroups();
                
-               $this->showEditUserGroupsForm( $username, $groups );
+               $this->showEditUserGroupsForm( $user, $groups );
 
-               if ($database == '' && $username{0} != '#') {
-                       $this->showLogFragment( User::newFromName($username), $wgOut );
-               }
+               // This isn't really ideal logging behavior,
+               // but let's not hide the interwiki logs if
+               // we're using them as is.
+               $this->showLogFragment( $user, $wgOut );
        }
 
-       function splitUsername( $username ) {
-               global $wgOut, $wgUser, $wgLocalDatabases;
+       /**
+        * Normalize the input username, which may be local or remote, and
+        * return a user (or proxy) object for manipulating it.
+        *
+        * Side effects: error output for invalid access
+        * @return mixed User, UserRightsProxy, or null
+        */
+       function fetchUser( $username ) {
+               global $wgOut, $wgUser;
 
                $parts = explode( '@', $username );
                if( count( $parts ) < 2 ) {
-                       $name = $username;
+                       $name = trim( $username );
                        $database = '';
                } else {
-                       list( $name, $database ) = $parts;
-               }
+                       list( $name, $database ) = array_map( 'trim', $parts );
 
-               if( $database != '' && !in_array( $database, $wgLocalDatabases ) ) {
-                       $wgOut->addWikiText( wfMsg( 'userrights-nodatabase', $database ) );
-                       return;
+                       if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) {
+                               $wgOut->addWikiText( wfMsg( 'userrights-no-interwiki' ) );
+                               return null;
+                       }
+                       if( !UserRightsProxy::validDatabase( $database ) ) {
+                               $wgOut->addWikiText( wfMsg( 'userrights-nodatabase', $database ) );
+                               return null;
+                       }
                }
-
+               
                if( $name == '' ) {
                        $wgOut->addWikiText( wfMsg( 'nouserspecified' ) );
-                       return;
+                       return false;
                }
-
-               if( $name{0} != '#' ) {
-                       # Avoid normalization when the input is a user ID
-                       $name = User::getCanonicalName( $name );
+               
+               if( $name{0} == '#' ) {
+                       // Numeric ID can be specified...
+                       // We'll do a lookup for the name internally.
+                       $id = intval( substr( $name, 1 ) );
+                       
+                       if( $database == '' ) {
+                               $name = User::whoIs( $id );
+                       } else {
+                               $name = UserRightsProxy::whoIs( $database, $id );
+                       }
+                       
                        if( !$name ) {
                                $wgOut->addWikiText( wfMsg( 'noname' ) );
-                               return;
+                               return null;
                        }
                }
-               $this->db =& $this->getDB( $database );
-               $this->userid = $this->getUserId( $name );
-
-               if( $this->userid == 0 ) {
-                       $wgOut->addWikiText( wfMsg( 'nosuchusershort', wfEscapeWikiText( $username ) ) );
-                       return;
-               }
-
-               if( $database != '' && !$wgUser->isAllowed('userrights-interwiki') ) {
-                       $wgOut->addWikiText( wfMsg( 'userrights-no-interwiki' ) );
-                       return;
-               }
-
-               return array( $database, $name );
-       }
-
-       /**
-        * Open a database connection to work on for the requested user.
-        * This may be a new connection to another database for remote users.
-        * @param string $database
-        * @return Database
-        */
-       function &getDB( $database ) {
+               
                if( $database == '' ) {
-                       $db =& wfGetDB( DB_MASTER );
+                       $user = User::newFromName( $name );
                } else {
-                       global $wgDBuser, $wgDBpassword;
-                       $server = $this->getMaster( $database );
-                       $db = new Database( $server, $wgDBuser, $wgDBpassword, $database );
-               }
-               return $db;
-       }
-       
-       /**
-        * Return the master server to connect to for the requested database.
-        */
-       function getMaster( $database ) {
-               global $wgDBserver, $wgAlternateMaster;
-               if( isset( $wgAlternateMaster[$database] ) ) {
-                       return $wgAlternateMaster[$database];
+                       $user = UserRightsProxy::newFromName( $database, $name );
                }
-               return $wgDBserver;
-       }
-
-       function getUserId( $name ) {
-               if( $name === '' )
-                       return 0;
-               return ( $name{0} == "#" )
-                       ? IntVal( substr( $name, 1 ) )
-                       : IntVal( $this->db->selectField( 'user',
-                               'user_id',
-                               array( 'user_name' => $name ),
-                               __METHOD__ ) );
-       }
-
-       function getUserGroups() {
-               $res = $this->db->select( 'user_groups',
-                       array( 'ug_group' ),
-                       array( 'ug_user' => $this->userid ),
-                       __METHOD__ );
-               $groups = array();
-               while( $row = $this->db->fetchObject( $res ) ) {
-                       $groups[] = $row->ug_group;
-               }
-               return $groups;
-       }
-
-       function addUserGroup( $group ) {
-               $this->db->insert( 'user_groups',
-                       array(
-                               'ug_user' => $this->userid,
-                               'ug_group' => $group,
-                       ),
-                       __METHOD__,
-                       array( 'IGNORE' ) );
-       }
-
-       function removeUserGroup( $group ) {
-               $this->db->delete( 'user_groups',
-                       array(
-                               'ug_user' => $this->userid,
-                               'ug_group' => $group,
-                       ),
-                       __METHOD__ );
-       }
-
-       function touchUser( $database ) {
-               $this->db->update( 'user',
-                       array( 'user_touched' => $this->db->timestamp() ),
-                       array( 'user_id' => $this->userid ),
-                       __METHOD__ );
                
-               global $wgMemc;
-               if ( function_exists( 'wfForeignMemcKey' ) ) {
-                       $key = wfForeignMemcKey( $database, false, 'user', 'id', $this->userid );
-               } else {
-                       $key = "$database:user:id:" . $this->userid;
+               if( !$user || $user->isAnon() ) {
+                       $wgOut->addWikiText( wfMsg( 'nosuchusershort', wfEscapeWikiText( $username ) ) );
+                       return null;
                }
-               $wgMemc->delete( $key );
+               
+               return $user;
        }
 
        function makeGroupNameList( $ids ) {
@@ -316,21 +255,22 @@ class UserrightsForm extends HTMLForm {
         * Show the form to edit group memberships.
         *
         * @todo make all CSS-y and semantic
-        * @param $username  String: Name of user you're editing
+        * @param $user      User or UserRightsProxy you're editing
         * @param $groups    Array:  Array of groups the user is in
         */
-       protected function showEditUserGroupsForm( $username, $groups ) {
+       protected function showEditUserGroupsForm( $user, $groups ) {
                global $wgOut, $wgUser;
                
                list( $addable, $removable ) = $this->splitGroups( $groups );
 
                $wgOut->addHTML(
                        Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->action, 'name' => 'editGroup' ) ) .
-                       Xml::hidden( 'user-editname', $username ) .
-                       Xml::hidden( 'wpEditToken', $wgUser->editToken( $username ) ) .
+                       Xml::hidden( 'user-editname', $user->getName() ) .
+                       Xml::hidden( 'wpEditToken', $wgUser->editToken( $user->getName() ) ) .
                        Xml::openElement( 'fieldset' ) .
                        Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) .
-                       $wgOut->parse( wfMsg( 'editinguser', $username ) ) .
+                       wfMsgExt( 'editinguser', array( 'parse' ),
+                               wfEscapeWikiText( $user->getName() ) ) .
                        $this->explainRights() .
                        "<table border='0'>
                        <tr>
diff --git a/includes/UserRightsProxy.php b/includes/UserRightsProxy.php
new file mode 100644 (file)
index 0000000..a9c6166
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+
+
+/**
+ * Cut-down copy of User interface for local-interwiki-database
+ * user rights manipulation.
+ */
+class UserRightsProxy {
+       private function __construct( $db, $database, $name, $id ) {
+               $this->db = $db;
+               $this->database = $database;
+               $this->name = $name;
+               $this->id = intval( $id );
+       }
+       
+       /**
+        * Confirm the selected database name is a valid local interwiki database name.
+        * @return bool
+        */
+       public static function validDatabase( $database ) {
+               global $wgLocalDatabases;
+               return in_array( $database, $wgLocalDatabases );
+       }
+       
+       public static function whoIs( $database, $id ) {
+               $user = self::newFromId( $database, $id );
+               if( $user ) {
+                       return $user->name;
+               } else {
+                       return false;
+               }
+       }
+       
+       /**
+        * Factory function; get a remote user entry by ID number.
+        * @return UserRightsProxy or null if doesn't exist
+        */
+       public static function newFromId( $database, $id ) {
+               return self::newFromLookup( $database, 'user_id', intval( $id ) );
+       }
+
+       public static function newFromName( $database, $name ) {
+               return self::newFromLookup( $database, 'user_name', $name );
+       }
+       
+       private static function newFromLookup( $database, $field, $value ) {
+               $db = self::getDB( $database );
+               if( $db ) {
+                       $row = $db->selectRow( 'user',
+                               array( 'user_id', 'user_name' ),
+                               array( $field => $value ),
+                               __METHOD__ );
+                       if( $row !== false ) {
+                               return new UserRightsProxy( $db, $database,
+                                       $row->user_name,
+                                       intval( $row->user_id ) );
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Open a database connection to work on for the requested user.
+        * This may be a new connection to another database for remote users.
+        * @param string $database
+        * @return Database or null if invalid selection
+        */
+       private static function getDB( $database ) {
+               global $wgLocalDatabases, $wgDBname;
+               if( self::validDatabase( $database ) ) {
+                       if( $database == $wgDBname ) {
+                               // Hmm... this shouldn't happen though. :)
+                               return wfGetDB( DB_MASTER );
+                       } else {
+                               global $wgDBuser, $wgDBpassword;
+                               $server = $this->getMaster( $database );
+                               return new Database( $server, $wgDBuser, $wgDBpassword, $database );
+                       }
+               }
+               return null;
+       }
+       
+       /**
+        * Return the master server to connect to for the requested database.
+        */
+       private function getMaster( $database ) {
+               global $wgDBserver, $wgAlternateMaster;
+               if( isset( $wgAlternateMaster[$database] ) ) {
+                       return $wgAlternateMaster[$database];
+               }
+               return $wgDBserver;
+       }
+       
+       public function getId() {
+               return $this->id;
+       }
+       
+       public function isAnon() {
+               return $this->getId() == 0;
+       }
+       
+       public function getName() {
+               return $this->name . '@' . $this->database;
+       }
+       
+       public function getUserPage() {
+               return Title::makeTitle( NS_USER, $this->getName() );
+       }
+       
+       // Replaces getUserGroups()
+       function getGroups() {
+               $res = $this->db->select( 'user_groups',
+                       array( 'ug_group' ),
+                       array( 'ug_user' => $this->id ),
+                       __METHOD__ );
+               $groups = array();
+               while( $row = $this->db->fetchObject( $res ) ) {
+                       $groups[] = $row->ug_group;
+               }
+               return $groups;
+       }
+       
+       // replaces addUserGroup
+       function addGroup( $group ) {
+               $this->db->insert( 'user_groups',
+                       array(
+                               'ug_user' => $this->id,
+                               'ug_group' => $group,
+                       ),
+                       __METHOD__,
+                       array( 'IGNORE' ) );
+       }
+       
+       // replaces removeUserGroup
+       function removeGroup( $group ) {
+               $this->db->delete( 'user_groups',
+                       array(
+                               'ug_user' => $this->id,
+                               'ug_group' => $group,
+                       ),
+                       __METHOD__ );
+       }
+       
+       // replaces touchUser
+       function invalidateCache() {
+               $this->db->update( 'user',
+                       array( 'user_touched' => $this->db->timestamp() ),
+                       array( 'user_id' => $this->id ),
+                       __METHOD__ );
+
+               global $wgMemc;
+               if ( function_exists( 'wfForeignMemcKey' ) ) {
+                       $key = wfForeignMemcKey( $this->database, false, 'user', 'id', $this->id );
+               } else {
+                       $key = "$this->database:user:id:" . $this->id;
+               }
+               $wgMemc->delete( $key );
+       }
+}
+
+?>
\ No newline at end of file