Board of Trustees vote
authorTim Starling <tstarling@users.mediawiki.org>
Sun, 9 May 2004 02:32:04 +0000 (02:32 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Sun, 9 May 2004 02:32:04 +0000 (02:32 +0000)
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/SpecialBoardvote.php [new file with mode: 0644]
languages/Language.php

index 8e0b269..1490f9f 100644 (file)
@@ -369,4 +369,11 @@ $wgTidyOpts = '';
 # See list of skins and their symbolic names in language/Language.php
 $wgDefaultSkin = "monobook";
 
+# Board of Trustees vote
+$wgBoardVoteDB = "boardvote";
+$wgContributingCandidates = array();
+$wgVolunteerCandidates = array();
+$wgGPGCommand = "gpg";
+$wgGPGRecipient = "boardvote";
+
 ?>
index fd823d0..d526064 100644 (file)
@@ -148,6 +148,46 @@ function wfMungeToUtf8($string) {
        return $string;
 }
 
+# Converts a single UTF-8 character into the corresponding HTML character entity
+function wfUtf8Entity( $char ) {
+       # Find the length
+       $z = ord( $char{0} );
+       if ( $z & 0x80 ) {
+               $length = 0;
+               while ( $z & 0x80 ) {
+                       $length++;
+                       $z <<= 1;
+               }
+       } else {
+               $length = 1;
+       }
+       
+       if ( $length != strlen( $char ) ) {
+               return "";
+       }
+       if ( $length == 1 ) {
+               return $char;
+       }
+
+       # Mask off the length-determining bits and shift back to the original location
+       $z &= 0xff;
+       $z >>= $length;
+
+       # Add in the free bits from subsequent bytes
+       for ( $i=1; $i<$length; $i++ ) {
+               $z <<= 6;
+               $z |= ord( $char{$i} ) & 0x3f;
+       }
+
+       # Make entity
+       return "&#$z;";
+}
+
+# Converts all multi-byte characters in a UTF-8 string into the appropriate character entity
+function wfUtf8ToHTML($string) {
+       return preg_replace_callback( '/[\\xc0-\\xfd][\\x80-\\xbf]*/', 'wfUtf8Entity', $string );
+}
+
 function wfDebug( $text, $logonly = false )
 {
        global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly;
@@ -736,7 +776,7 @@ function wfEscapeShellArg( )
                }
 
                if (substr(php_uname(), 0, 7) == "Windows") {
-                       $retVal .= "\"$arg\"";
+                       $retVal .= '"' . str_replace( '"','\"', $arg ) . '"';
                } else {
                        $retVal .= escapeshellarg( $arg );
                }
diff --git a/includes/SpecialBoardvote.php b/includes/SpecialBoardvote.php
new file mode 100644 (file)
index 0000000..1555eb4
--- /dev/null
@@ -0,0 +1,347 @@
+<?php
+
+function wfSpecialBoardvote( $par = "" ) {
+       global $wgRequest, $wgBoardVoteDB;
+
+       $form = new BoardVoteForm( $wgRequest, $wgBoardVoteDB );
+       if ( $par ) {
+               $form->mAction = $par;
+       }
+       
+       $form->execute();
+}
+
+class BoardVoteForm {
+       var $mPosted, $mContributing, $mVolunteer, $mDBname, $mUserDays, $mUserEdits;
+       var $mHasVoted, $mAction, $mUserKey;
+
+       function BoardVoteForm( &$request, $dbName ) {
+               global $wgUser, $wgDBname, $wgInputEncoding;
+
+               $this->mUserKey = iconv( $wgInputEncoding, "UTF-8", $wgUser->getName() ) . "@$wgDBname";
+               $this->mPosted = $request->wasPosted();
+               $this->mContributing = $request->getInt( "contributing" );
+               $this->mVolunteer = $request->getInt( "volunteer" );
+               $this->mDBname = $dbName;
+               $this->mHasVoted = $this->hasVoted( $wgUser );
+               $this->mAction = $request->getText( "action" );
+       }
+
+       function execute() {
+               global $wgUser;
+               if ( $this->mAction == "list" ) {
+                       $this->displayList();
+               } elseif ( $this->mAction == "dump" ) {
+                       $this->dump();
+               } elseif( $this->mAction == "vote" ) {
+                       if ( !$wgUser->getID() ) {
+                               $this->notLoggedIn();
+                       } else {
+                               $this->getQualifications( $wgUser );
+                               if ( $this->mUserDays < 90 ) {
+                                       $this->notQualified();
+                               } elseif ( $this->mPosted ) {
+                                       $this->logVote();
+                               } else {
+                                       $this->displayVote();
+                               }
+                       }
+               } else {
+                       $this->displayEntry();
+               }
+       }
+       
+       function displayEntry() {
+               global $wgOut;
+               $wgOut->addWikiText( wfMsg( "boardvote_entry" ) );
+       }
+
+       function hasVoted( &$user ) {
+               global $wgDBname;
+               $row = wfGetArray( $this->mDBname . ".vote", array( "1" ), 
+                 array( "vote_key" => $this->mUserKey ), "BoardVoteForm::getUserVote" );
+               if ( $row === false ) {
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       function logVote() {
+               global $wgUser, $wgDBname, $wgIP, $wgOut;
+               $now = wfTimestampNow();
+               $record = $this->encrypt( $this->mContributing, $this->mVolunteer );
+               $db = $this->mDBname;
+
+               # Add vote to log
+               wfInsertArray( "$db.log", array(
+                       "log_user" => $wgUser->getID(),
+                       "log_user_text" => $wgUser->getName(),
+                       "log_user_key" => $this->mUserKey,
+                       "log_wiki" => $wgDBname,
+                       "log_edits" => $this->mUserEdits,
+                       "log_days" => $this->mUserDays,
+                       "log_record" => $record,
+                       "log_ip" => $wgIP,
+                       "log_xff" => @$_SERVER['HTTP_X_FORWARDED_FOR'],
+                       "log_ua" => $_SERVER['HTTP_USER_AGENT'],
+                       "log_timestamp" => $now
+               ));
+
+               # Record vote in non-duplicating vote table
+               $sql = "REPLACE INTO $db.vote (vote_key,vote_record) " .
+                 "VALUES ('". wfStrencode( $this->mUserKey ) . "','" . wfStrencode( $record ) . "')";
+               wfQuery( $sql, DB_WRITE );
+
+               $wgOut->addWikiText( wfMsg( "boardvote_entered", $record ) );
+       }
+       
+       function displayVote() {
+               global $wgContributingCandidates, $wgVolunteerCandidates, $wgOut;
+               
+               $thisTitle = Title::makeTitle( NS_SPECIAL, "Boardvote" );
+               $action = $thisTitle->getLocalURL( "action=vote" );
+               if ( $this->mHasVoted ) {
+                       $intro = wfMsg( "boardvote_intro_change" );
+               } else {
+                       $intro = wfMsg( "boardvote_intro" );
+               }
+               $contributing = wfMsg( "boardvote_contributing" );
+               $volunteer = wfMsg( "boardvote_volunteer" );
+               $ok = wfMsg( "ok" );
+               
+               $candidatesV = $candidatesC = array();
+               foreach( $wgContributingCandidates as $i => $candidate ) {
+                       $candidatesC[] = array( $i, $candidate );
+               }
+               foreach ( $wgVolunteerCandidates as $i => $candidate ) {
+                       $candidatesV[] = array( $i, $candidate );
+               }
+
+               srand ((float)microtime()*1000000);
+               shuffle( $candidatesC );
+               shuffle( $candidatesV );
+
+               $text = "
+                 $intro
+                 <form name=\"boardvote\" id=\"boardvote\" method=\"post\" action=\"$action\">
+                 <table border='0'><tr><td colspan=2>
+                 <h2>$contributing</h2>
+                 </td></tr>";
+               $text .= $this->voteEntry( -1, wfMsg( "boardvote_abstain" ), "contributing" );
+               foreach ( $candidatesC as $candidate ) {
+                       $text .= $this->voteEntry( $candidate[0], $candidate[1], "contributing" );
+               }
+               $text .= "
+                 <tr><td colspan=2>
+                 <h2>$volunteer</h2></td></tr>";
+               $text .= $this->voteEntry( -1, wfMsg( "boardvote_abstain" ), "volunteer" );
+               foreach ( $candidatesV as $candidate ) {
+                       $text .= $this->voteEntry( $candidate[0], $candidate[1], "volunteer" );
+               }
+               
+               $text .= "<tr><td>&nbsp;</td><td>
+                 <input name=\"submit\" type=\"submit\" value=\"$ok\">
+                 </td></tr></table></form>";
+               $text .= wfMsg( "boardvote_footer" );
+               $wgOut->addHTML( $text );
+       }
+
+       function voteEntry( $index, $candidate, $name ) {
+               if ( $index == -1 ) {
+                       $checked = " CHECKED";
+               } else {
+                       $checked = "";
+               }
+
+               return "
+               <tr><td align=\"right\">
+                 <input type=\"radio\" name=\"$name\" value=\"$index\"$checked>
+               </td><td align=\"left\">
+                 $candidate
+               </td></tr>";
+       }
+
+       function notLoggedIn() {
+               global $wgOut;
+               $wgOut->addWikiText( wfMsg( "boardvote_notloggedin" ) );
+       }
+       
+       function notQualified() {
+               global $wgOut;
+               $wgOut->addWikiText( wfMsg( "boardvote_notqualified", $this->mUserDays ) );
+       }
+       
+       function encrypt( $contributing, $volunteer ) {
+               global $wgVolunteerCandidates, $wgContributingCandidates;
+               global $wgGPGCommand, $wgGPGRecipient;
+               $file = @fopen( "/dev/urandom", "r" );
+               if ( $file ) {
+                       $salt = implode( "", unpack( fread( $file, 64 ), "H*" ));
+                       fclose( $file );
+               } else {
+                       $salt = Parser::getRandomString() . Parser::getRandomString();
+               }
+               $record = 
+                 "Contributing: $contributing (" .$wgContributingCandidates[$contributing] . ")\n" .
+                 "Volunteer: $volunteer (" . $wgVolunteerCandidates[$volunteer] . ")\n" .
+                 "User: {$this->mUserKey}\n" .
+                 "Salt: $salt\n";
+               # Get file names
+               $input = tempnam( "/tmp", "gpg_" );
+               $output = tempnam( "/tmp", "gpg_" );
+
+               # Write unencrypted record
+               $file = fopen( $input, "w" );
+               fwrite( $file, $record );
+               fclose( $file );
+
+               # Call GPG
+               $command = wfEscapeShellArg( $wgGPGCommand ) . " --batch --yes -ear " . 
+                 wfEscapeShellArg( $wgGPGRecipient ) . " -o " . wfEscapeShellArg( $output, $input );
+
+               shell_exec( $command );
+
+               # Read result
+               $result = file_get_contents( $output );
+
+               # Delete temporary files
+               #unlink( $input );
+               #unlink( $output );
+               
+               return $result;
+       }
+
+       function getQualifications( &$user ) {
+               $id = $user->getID();
+               if ( !$id ) {
+                       $this->mUserDays = 0;
+                       $this->mUserEdits = 0;
+                       return;
+               }
+
+               # Count contributions and find earliest edit
+               # First cur
+               $sql = "SELECT COUNT(*) as n, MIN(cur_timestamp) as t FROM cur WHERE cur_user=$id";
+               $res = wfQuery( $sql, DB_READ, "BoardVoteForm::getQualifications" );
+               $cur = wfFetchObject( $res );
+               wfFreeResult( $res );
+
+               # If the user has stacks of contributions, don't check old as well
+               $signup = wfTimestamp2Unix( $cur->t );
+               $now = time();
+               $days = ($now - $signup) / 86400;
+               if ( $cur->n > 400 && $days > 180 ) {
+                       $this->mUserDays = 0x7fffffff;
+                       $this->mUserEdits = 0x7fffffff;
+                       return;
+               }
+
+               # Now check old
+               $sql = "SELECT COUNT(*) as n, MIN(old_timestamp) as t FROM old WHERE old_user=$id";
+               $res = wfQuery( $sql, DB_READ, "BoardVoteForm::getQualifications" );
+               $old = wfFetchObject( $res );
+               wfFreeResult( $res );
+               
+               $signup = min( wfTimestamp2Unix( $old->t ), $signup );
+               $this->mUserDays = (int)(($now - $signup) / 86400);
+               $this->mUserEdits = $cur->n + $old->n;
+       }
+       
+       function displayList() {
+               global $wgOut, $wgOutputEncoding, $wgLang, $wgUser;
+               $sql = "SELECT log_timestamp,log_user_key FROM {$this->mDBname}.log";
+               $res = wfQuery( $sql, DB_READ, "BoardVoteForm::list" );
+               if ( wfNumRows( $res ) == 0 ) {
+                       $wgOut->addWikiText( wfMsg( "boardvote_novotes" ) );
+                       return;
+               }
+               $thisTitle = Title::makeTitle( NS_SPECIAL, "Boardvote" );
+               $sk = $wgUser->getSkin();
+               $dumpLink = $sk->makeKnownLinkObj( $thisTitle, wfMsg( "boardvote_dumplink" ), "action=dump" );
+               
+               $intro = wfMsg( "boardvote_listintro", $dumpLink );
+               $hTime = wfMsg( "boardvote_time" );
+               $hUser = wfMsg( "boardvote_user" );
+               $hContributing = wfMsg( "boardvote_contributing" );
+               $hVolunteer = wfMsg( "boardvote_volunteer" );
+
+               $s = "$intro <table border=0><tr><th>
+                   $hTime
+                 </th><th>
+                   $hUser
+                 </th></tr>";
+
+               while ( $row = wfFetchObject( $res ) ) {
+                       if ( $wgOutputEncoding != "utf-8" ) {
+                               $user = wfUtf8ToHTML( $row->log_user_key );
+                       } else {
+                               $user = $row->log_user_key;
+                       }
+                       $time = $wgLang->timeanddate( $row->log_timestamp );
+                       $s .= "<tr><td>
+                           $time
+                         </td><td>
+                           $user
+                         </td></tr>";
+               }
+               $s .= "</table>";
+               $wgOut->addHTML( $s );
+       }
+
+       function dump() {
+               global $wgOut, $wgOutputEncoding, $wgLang, $wgUser;
+
+               $userRights = $wgUser->getRights();
+               if ( in_array( "boardvote", $userRights ) ) {
+                       $admin = true;
+               } else {
+                       $admin = false;
+               }
+               
+               $sql = "SELECT * FROM {$this->mDBname}.log";
+               $res = wfQuery( $sql, DB_READ, "BoardVoteForm::list" );
+               if ( wfNumRows( $res ) == 0 ) {
+                       $wgOut->addWikiText( wfMsg( "boardvote_novotes" ) );
+                       return;
+               }
+               $s = "<table border=1>";
+               if ( $admin ) {
+                       $s .= wfMsg( "boardvote_dumpheader" );
+               }
+               
+               while ( $row = wfFetchObject( $res ) ) {
+                       if ( $wgOutputEncoding != "utf-8" ) {
+                               $user = wfUtf8ToHTML( $row->log_user_key );
+                       } else {
+                               $user = $row->log_user_key;
+                       }
+                       $time = $wgLang->timeanddate( $row->log_timestamp );
+                       $record = nl2br( $row->log_record );
+                       if ( $admin ) {
+                               $edits = $row->log_edits == 0x7fffffff ? "many" : $row->log_edits;
+                               $days = $row->log_days == 0x7fffffff ? "many" : $row->log_days;
+                               $s .= "<tr><td>
+                                 $time
+                               </td><td>
+                                 $user
+                               </td><td>
+                                 $edits
+                               </td><td>
+                                 $days
+                               </td><td>
+                                 {$row->log_ip}
+                               </td><td>
+                                 {$row->log_ua}
+                               </td><td>
+                                 $record
+                               </td></tr>";
+                       } else {
+                               $s .= "<tr><td>$time</td><td>$user</td><td>$record</td></tr>";
+                       }
+               }
+               $s .= "</table>";
+               $wgOut->addHTML( $s );
+       }
+}
+?>
index b51b08f..4520ec8 100644 (file)
@@ -422,7 +422,8 @@ $wgLanguageNamesEn =& $wgLanguageNames;
        "Export"                => "XML page export",
        "Version"               => "Show MediaWiki version",
        "Allmessages"   => "All system messages",
-       "Search"                => "",
+       "Search"                => ""
+#      "Boardvote"     => "Wikimedia Foundation Board of Trustees election"
 );
 
 /* private */ $wgSysopSpecialPagesEn = array(
@@ -1590,6 +1591,41 @@ amusement.",
 "and" => "and",
 "othercontribs" => "Based on work by $1.",
 "siteusers" => "$wgSitename user(s) $1",
+
+# Board vote
+"boardvote" => "Wikimedia Board of Trustees election",
+"boardvote_entry" => 
+"* [[Special:Boardvote/vote|Vote]]
+* [[Special:Boardvote/list|List votes to date]]
+* [[Special:Boardvote/dump|Dump encrypted election record]]",
+"boardvote_intro" => "<p>Please choose your preferred candidate for both the 
+Contributing Representative and the Volunteer Representative.</p>",
+"boardvote_intro_change" => "<p>You have voted before. However you may change 
+your vote using the form below.</p>",
+"boardvote_abstain" => "Abstain",
+"boardvote_footer" => "&nbsp;",
+"boardvote_entered" => "Thank you, your vote has been recorded.
+
+Following is your encrypted vote record. It will appear publicly at [[Special:Boardvote/dump]]. 
+
+<pre>$1</pre>
+
+[[Special:Boardvote/entry|Back]]",
+"boardvote_notloggedin" => "You are not logged in. To vote, you must use an account
+which has existed for at least 90 days.",
+"boardvote_notqualified" => "Sorry, your first contribution was only $1 days ago. 
+You need to have been contributing for at least 90 days to vote in this election.",
+"boardvote_novotes" => "Nobody has voted yet.",
+"boardvote_contributing" => "Contributing candidate",
+"boardvote_volunteer" => "Volunteer candidate",
+"boardvote_time" => "Time",
+"boardvote_user" => "User",
+"boardvote_listintro" => "<p>This is a list of all votes which have been recorded 
+to date. $1 for the full encrypted election record.</p>",
+"boardvote_dumplink" => "Click here",
+"boardvote_dumpheader" => "<caption><strong>Election administrator private dump</strong></caption>
+<tr><th>Time</th><th>User</th><th>Edits</th><th>Days</th>
+<th>IP</th><th>User agent</th><th>Record</th></tr>"
 );
 
 #--------------------------------------------------------------------------