4659ddb2d9c3f2d09c8bcb588d7552eb1a317c04
3 # Wikimedia Foundation Board of Trustees Election
6 $wgExtensionFunctions[] = "wfBoardvoteSetup";
9 function wfBoardvoteSetup()
11 # Look out, freaky indenting
12 # All this happens after SpecialPage.php has been included in Setup.php
14 class BoardVotePage
extends SpecialPage
{
15 var $mPosted, $mContributing, $mVolunteer, $mDBname, $mUserDays, $mUserEdits;
16 var $mHasVoted, $mAction, $mUserKey;
18 function BoardVotePage() {
19 SpecialPage
::SpecialPage( "Boardvote" );
22 function execute( $par ) {
23 global $wgUser, $wgDBname, $wgInputEncoding, $wgRequest, $wgBoardVoteDB;
25 $this->mUserKey
= iconv( $wgInputEncoding, "UTF-8", $wgUser->getName() ) . "@$wgDBname";
26 $this->mPosted
= $wgRequest->wasPosted();
27 $this->mContributing
= $wgRequest->getInt( "contributing" );
28 $this->mVolunteer
= $wgRequest->getInt( "volunteer" );
29 $this->mDBname
= $wgBoardVoteDB;
30 $this->mHasVoted
= $this->hasVoted( $wgUser );
33 $this->mAction
= $par;
35 $this->mAction
= $wgRequest->getText( "action" );
40 if ( $this->mAction
== "list" ) {
42 } elseif ( $this->mAction
== "dump" ) {
44 } elseif( $this->mAction
== "vote" ) {
45 if ( !$wgUser->getID() ) {
48 $this->getQualifications( $wgUser );
49 if ( $this->mUserDays
< 90 ) {
50 $this->notQualified();
51 } elseif ( $this->mPosted
) {
58 $this->displayEntry();
62 function displayEntry() {
64 $wgOut->addWikiText( wfMsg( "boardvote_entry" ) );
67 function hasVoted( &$user ) {
69 $row = wfGetArray( $this->mDBname
. ".log", array( "1" ),
70 array( "log_user_key" => $this->mUserKey
), "BoardVotePage::getUserVote" );
71 if ( $row === false ) {
79 global $wgUser, $wgDBname, $wgIP, $wgOut;
80 $fname = "BoardVotePage::logVote";
82 $now = wfTimestampNow();
83 $record = $this->encrypt( $this->mContributing
, $this->mVolunteer
);
86 # Mark previous votes as old
87 $encKey = wfStrencode( $this->mUserKey
);
88 $sql = "UPDATE $db.log SET log_current=0 WHERE log_user_key='$encKey'";
89 wfQuery( $sql, DB_WRITE
, $fname );
92 wfInsertArray( "$db.log", array(
93 "log_user" => $wgUser->getID(),
94 "log_user_text" => $wgUser->getName(),
95 "log_user_key" => $this->mUserKey
,
96 "log_wiki" => $wgDBname,
97 "log_edits" => $this->mUserEdits
,
98 "log_days" => $this->mUserDays
,
99 "log_record" => $record,
101 "log_xff" => @$_SERVER['HTTP_X_FORWARDED_FOR'],
102 "log_ua" => $_SERVER['HTTP_USER_AGENT'],
103 "log_timestamp" => $now,
107 $wgOut->addWikiText( wfMsg( "boardvote_entered", $record ) );
110 function displayVote() {
111 global $wgContributingCandidates, $wgVolunteerCandidates, $wgOut;
113 $thisTitle = Title
::makeTitle( NS_SPECIAL
, "Boardvote" );
114 $action = $thisTitle->getLocalURL( "action=vote" );
115 if ( $this->mHasVoted
) {
116 $intro = wfMsg( "boardvote_intro_change" );
118 $intro = wfMsg( "boardvote_intro" );
120 $contributing = wfMsg( "boardvote_contributing" );
121 $volunteer = wfMsg( "boardvote_volunteer" );
124 $candidatesV = $candidatesC = array();
125 foreach( $wgContributingCandidates as $i => $candidate ) {
126 $candidatesC[] = array( $i, $candidate );
128 foreach ( $wgVolunteerCandidates as $i => $candidate ) {
129 $candidatesV[] = array( $i, $candidate );
132 srand ((float)microtime()*1000000);
133 shuffle( $candidatesC );
134 shuffle( $candidatesV );
138 <form name=\"boardvote\" id=\"boardvote\" method=\"post\" action=\"$action\">
139 <table border='0'><tr><td colspan=2>
140 <h2>$contributing</h2>
142 $text .= $this->voteEntry( -1, wfMsg( "boardvote_abstain" ), "contributing" );
143 foreach ( $candidatesC as $candidate ) {
144 $text .= $this->voteEntry( $candidate[0], $candidate[1], "contributing" );
148 <h2>$volunteer</h2></td></tr>";
149 $text .= $this->voteEntry( -1, wfMsg( "boardvote_abstain" ), "volunteer" );
150 foreach ( $candidatesV as $candidate ) {
151 $text .= $this->voteEntry( $candidate[0], $candidate[1], "volunteer" );
154 $text .= "<tr><td> </td><td>
155 <input name=\"submit\" type=\"submit\" value=\"$ok\">
156 </td></tr></table></form>";
157 $text .= wfMsg( "boardvote_footer" );
158 $wgOut->addHTML( $text );
161 function voteEntry( $index, $candidate, $name ) {
162 if ( $index == -1 ) {
163 $checked = " CHECKED";
169 <tr><td align=\"right\">
170 <input type=\"radio\" name=\"$name\" value=\"$index\"$checked>
171 </td><td align=\"left\">
176 function notLoggedIn() {
178 $wgOut->addWikiText( wfMsg( "boardvote_notloggedin" ) );
181 function notQualified() {
183 $wgOut->addWikiText( wfMsg( "boardvote_notqualified", $this->mUserDays
) );
186 function encrypt( $contributing, $volunteer ) {
187 global $wgVolunteerCandidates, $wgContributingCandidates;
188 global $wgGPGCommand, $wgGPGRecipient, $wgGPGHomedir;
189 $file = @fopen
( "/dev/urandom", "r" );
191 $salt = implode( "", unpack( "H*", fread( $file, 64 ) ));
194 $salt = Parser
::getRandomString() . Parser
::getRandomString();
197 "Contributing: $contributing (" .$wgContributingCandidates[$contributing] . ")\n" .
198 "Volunteer: $volunteer (" . $wgVolunteerCandidates[$volunteer] . ")\n" .
201 $input = tempnam( "/tmp", "gpg_" );
202 $output = tempnam( "/tmp", "gpg_" );
204 # Write unencrypted record
205 $file = fopen( $input, "w" );
206 fwrite( $file, $record );
210 $command = wfEscapeShellArg( $wgGPGCommand ) . " --batch --yes -ear " .
211 wfEscapeShellArg( $wgGPGRecipient ) . " -o " . wfEscapeShellArg( $output );
212 if ( $wgGPGHomedir ) {
213 $command .= " --homedir " . wfEscapeShellArg( $wgGPGHomedir );
215 $command .= " " . wfEscapeShellArg( $input );
217 shell_exec( $command );
220 $result = file_get_contents( $output );
222 # Delete temporary files
229 function getQualifications( &$user ) {
230 $id = $user->getID();
232 $this->mUserDays
= 0;
233 $this->mUserEdits
= 0;
237 # Count contributions and find earliest edit
239 $sql = "SELECT COUNT(*) as n, MIN(cur_timestamp) as t FROM cur WHERE cur_user=$id";
240 $res = wfQuery( $sql, DB_READ
, "BoardVotePage::getQualifications" );
241 $cur = wfFetchObject( $res );
242 wfFreeResult( $res );
244 # If the user has stacks of contributions, don't check old as well
246 if ( is_null( $cur->t
) ) {
249 $signup = wfTimestamp2Unix( $cur->t
);
252 $days = ($now - $signup) / 86400;
253 if ( $cur->n
> 400 && $days > 180 ) {
254 $this->mUserDays
= 0x7fffffff;
255 $this->mUserEdits
= 0x7fffffff;
260 $sql = "SELECT COUNT(*) as n, MIN(old_timestamp) as t FROM old WHERE old_user=$id";
261 $res = wfQuery( $sql, DB_READ
, "BoardVotePage::getQualifications" );
262 $old = wfFetchObject( $res );
263 wfFreeResult( $res );
265 if ( !is_null( $old->t
) ) {
266 $signup = min( wfTimestamp2Unix( $old->t
), $signup );
268 $this->mUserDays
= (int)(($now - $signup) / 86400);
269 $this->mUserEdits
= $cur->n +
$old->n
;
272 function displayList() {
273 global $wgOut, $wgOutputEncoding, $wgLang, $wgUser;
274 $sql = "SELECT log_timestamp,log_user_key FROM {$this->mDBname}.log ORDER BY log_user_key";
275 $res = wfQuery( $sql, DB_READ
, "BoardVotePage::list" );
276 if ( wfNumRows( $res ) == 0 ) {
277 $wgOut->addWikiText( wfMsg( "boardvote_novotes" ) );
280 $thisTitle = Title
::makeTitle( NS_SPECIAL
, "Boardvote" );
281 $sk = $wgUser->getSkin();
282 $dumpLink = $sk->makeKnownLinkObj( $thisTitle, wfMsg( "boardvote_dumplink" ), "action=dump" );
284 $intro = wfMsg( "boardvote_listintro", $dumpLink );
285 $hTime = wfMsg( "boardvote_time" );
286 $hUser = wfMsg( "boardvote_user" );
287 $hContributing = wfMsg( "boardvote_contributing" );
288 $hVolunteer = wfMsg( "boardvote_volunteer" );
290 $s = "$intro <table border=1><tr><th>
296 while ( $row = wfFetchObject( $res ) ) {
297 if ( $wgOutputEncoding != "utf-8" ) {
298 $user = wfUtf8ToHTML( $row->log_user_key
);
300 $user = $row->log_user_key
;
302 $time = $wgLang->timeanddate( $row->log_timestamp
);
310 $wgOut->addHTML( $s );
314 global $wgOut, $wgOutputEncoding, $wgLang, $wgUser;
316 $userRights = $wgUser->getRights();
317 if ( in_array( "boardvote", $userRights ) ) {
323 $sql = "SELECT * FROM {$this->mDBname}.log";
324 $res = wfQuery( $sql, DB_READ
, "BoardVotePage::list" );
325 if ( wfNumRows( $res ) == 0 ) {
326 $wgOut->addWikiText( wfMsg( "boardvote_novotes" ) );
329 $s = "<table border=1>";
331 $s .= wfMsg( "boardvote_dumpheader" );
334 while ( $row = wfFetchObject( $res ) ) {
335 if ( $wgOutputEncoding != "utf-8" ) {
336 $user = wfUtf8ToHTML( $row->log_user_key
);
338 $user = $row->log_user_key
;
340 $time = $wgLang->timeanddate( $row->log_timestamp
);
341 $record = nl2br( $row->log_record
);
343 $edits = $row->log_edits
== 0x7fffffff ?
"many" : $row->log_edits
;
344 $days = $row->log_days
== 0x7fffffff ?
"many" : $row->log_days
;
361 $s .= "<tr><td>$time</td><td>$user</td><td>$record</td></tr>";
365 $wgOut->addHTML( $s );
369 SpecialPage
::addPage( new BoardVotePage
);
371 global $wgMessageCache;
372 $wgMessageCache->addMessages( array(
374 "boardvote" => "Wikimedia Board of Trustees election",
376 "* [[Special:Boardvote/vote|Vote]]
377 * [[Special:Boardvote/list|List votes to date]]
378 * [[Special:Boardvote/dump|Dump encrypted election record]]",
379 "boardvote_intro" => "<p>Please choose your preferred candidate for both the
380 Contributing Representative and the Volunteer Representative.</p>",
381 "boardvote_intro_change" => "<p>You have voted before. However you may change
382 your vote using the form below.</p>",
383 "boardvote_abstain" => "Abstain",
384 "boardvote_footer" => " ",
385 "boardvote_entered" => "Thank you, your vote has been recorded.
387 Following is your encrypted vote record. It will appear publicly at [[Special:Boardvote/dump]].
391 [[Special:Boardvote/entry|Back]]",
392 "boardvote_notloggedin" => "You are not logged in. To vote, you must use an account
393 which has existed for at least 90 days.",
394 "boardvote_notqualified" => "Sorry, your first contribution was only $1 days ago.
395 You need to have been contributing for at least 90 days to vote in this election.",
396 "boardvote_novotes" => "Nobody has voted yet.",
397 "boardvote_contributing" => "Contributing candidate",
398 "boardvote_volunteer" => "Volunteer candidate",
399 "boardvote_time" => "Time",
400 "boardvote_user" => "User",
401 "boardvote_listintro" => "<p>This is a list of all votes which have been recorded
402 to date. $1 for the full encrypted election record.</p>",
403 "boardvote_dumplink" => "Click here",
404 "boardvote_dumpheader" => "<caption><strong>Election administrator private dump</strong></caption>
405 <tr><th>Time</th><th>User</th><th>Edits</th><th>Days</th>
406 <th>IP</th><th>User agent</th><th>Record</th></tr>"
410 } # End of extension function