Index: trunk/extensions/SecurePoll/SecurePoll.js |
— | — | @@ -1,111 +0,0 @@ |
2 | | -function securepoll_strike_popup(e, action, id) { |
3 | | - var pop = document.getElementById('securepoll-popup'); |
4 | | - |
5 | | - var e = window.event || e; |
6 | | - if(!e) return; |
7 | | - var target = e.target || e.srcElement; |
8 | | - if(!target) return; |
9 | | - |
10 | | - if ( pop.parentNode.tagName.toLowerCase() != 'body' ) { |
11 | | - pop = pop.parentNode.removeChild( pop ); |
12 | | - pop = document.body.appendChild( pop ); |
13 | | - } |
14 | | - |
15 | | - var left = 0; |
16 | | - var top = 0; |
17 | | - var containing = target; |
18 | | - while ( containing ) { |
19 | | - left += containing.offsetLeft; |
20 | | - top += containing.offsetTop; |
21 | | - containing = containing.offsetParent; |
22 | | - } |
23 | | - left += target.offsetWidth - 10; |
24 | | - top += target.offsetHeight - 10; |
25 | | - |
26 | | - // Show the appropriate button |
27 | | - var strikeButton = document.getElementById( 'securepoll-strike-button' ); |
28 | | - var unstrikeButton = document.getElementById( 'securepoll-unstrike-button' ); |
29 | | - if ( action == 'strike' ) { |
30 | | - strikeButton.style.display = 'inline'; |
31 | | - strikeButton.disabled = false; |
32 | | - unstrikeButton.style.display = 'none'; |
33 | | - unstrikeButton.disabled = true; |
34 | | - } else { |
35 | | - unstrikeButton.style.display = 'inline'; |
36 | | - unstrikeButton.disabled = false; |
37 | | - strikeButton.style.display = 'none'; |
38 | | - strikeButton.disabled = true; |
39 | | - } |
40 | | - document.getElementById( 'securepoll-strike-result' ).innerHTML = ''; |
41 | | - |
42 | | - // Set the hidden fields for submission |
43 | | - document.getElementById( 'securepoll-action').value = action; |
44 | | - document.getElementById( 'securepoll-vote-id' ).value = id; |
45 | | - |
46 | | - // Show the popup |
47 | | - pop.style.left = left + 'px'; |
48 | | - pop.style.top = top + 'px'; |
49 | | - pop.style.display = 'block'; |
50 | | - |
51 | | - // Focus on the reason box |
52 | | - var reason = document.getElementById( 'securepoll-strike-reason' ); |
53 | | - reason.focus(); |
54 | | - reason.select(); |
55 | | -} |
56 | | - |
57 | | -function securepoll_strike(action) { |
58 | | - var popup = document.getElementById('securepoll-popup'); |
59 | | - if(action == 'cancel') { |
60 | | - popup.style.display = ''; |
61 | | - return; |
62 | | - } |
63 | | - if ( action == 'submit' ) { |
64 | | - action = document.getElementById( 'securepoll-action' ).value; |
65 | | - } |
66 | | - var id = document.getElementById( 'securepoll-vote-id' ).value; |
67 | | - var strikeButton = document.getElementById( 'securepoll-strike-button' ); |
68 | | - var unstrikeButton = document.getElementById( 'securepoll-unstrike-button' ); |
69 | | - var spinner = document.getElementById( 'securepoll-strike-spinner' ); |
70 | | - strikeButton.disabled = true; |
71 | | - unstrikeButton.disabled = true; |
72 | | - spinner.style.display = 'block'; |
73 | | - var reason = document.getElementById( 'securepoll-strike-reason' ).value; |
74 | | - |
75 | | - var processResult = function (xhr) { |
76 | | - spinner.style.display = 'none'; |
77 | | - strikeButton.disabled = false; |
78 | | - unstrikeButton.disabled = false; |
79 | | - |
80 | | - if ( xhr.status >= 300 || xhr.status < 200 ) { |
81 | | - document.getElementById( 'securepoll-strike-result' ).innerHTML = xhr.responseText; |
82 | | - return; |
83 | | - } |
84 | | - |
85 | | - // Evaluate JSON result, with brackets to avoid interpretation as a code block |
86 | | - result = eval( '(' + xhr.responseText + ')' ); |
87 | | - if ( result.status == 'good' ) { |
88 | | - popup.style.display = 'none'; |
89 | | - } else { |
90 | | - document.getElementById( 'securepoll-strike-result' ).innerHTML = result.message; |
91 | | - } |
92 | | - |
93 | | - securepoll_modify_document( action, id ); |
94 | | - }; |
95 | | - |
96 | | - sajax_do_call( 'wfSecurePollStrike', [ action, id, reason ], processResult ); |
97 | | -} |
98 | | - |
99 | | -function securepoll_modify_document( action, voteId ) { |
100 | | - var popupButton = document.getElementById( 'securepoll-popup-' + voteId ); |
101 | | - var row = popupButton.parentNode.parentNode; |
102 | | - if ( action == 'strike' ) { |
103 | | - row.className += ' securepoll-struck-vote'; |
104 | | - popupButton.value = securepoll_unstrike_button; |
105 | | - } else { |
106 | | - row.className = row.className.replace( 'securepoll-struck-vote', '' ); |
107 | | - popupButton.value = securepoll_strike_button; |
108 | | - } |
109 | | - popupButton.onclick = function (event) { |
110 | | - securepoll_strike_popup( event, action == 'strike' ? 'unstrike' : 'strike', voteId ); |
111 | | - } |
112 | | -} |
Index: trunk/extensions/SecurePoll/SecurePoll_body.php |
— | — | @@ -1,86 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) { |
4 | | - die( "Not a valid entry point\n" ); |
5 | | -} |
6 | | - |
7 | | -class SecurePollPage extends UnlistedSpecialPage { |
8 | | - static $pages = array( |
9 | | - 'details' => 'SecurePoll_DetailsPage', |
10 | | - 'dump' => 'SecurePoll_DumpPage', |
11 | | - 'entry' => 'SecurePoll_EntryPage', |
12 | | - 'list' => 'SecurePoll_ListPage', |
13 | | - 'login' => 'SecurePoll_LoginPage', |
14 | | - 'msgdump' => 'SecurePoll_MessageDumpPage', |
15 | | - 'tally' => 'SecurePoll_TallyPage', |
16 | | - 'translate' => 'SecurePoll_TranslatePage', |
17 | | - 'vote' => 'SecurePoll_VotePage', |
18 | | - ); |
19 | | - |
20 | | - /** |
21 | | - * Constructor |
22 | | - */ |
23 | | - public function __construct() { |
24 | | - parent::__construct( 'SecurePoll' ); |
25 | | - } |
26 | | - |
27 | | - /** |
28 | | - * Show the special page |
29 | | - * |
30 | | - * @param $paramString Mixed: parameter passed to the page or null |
31 | | - */ |
32 | | - public function execute( $paramString ) { |
33 | | - global $wgOut, $wgRequest, $wgScriptPath; |
34 | | - |
35 | | - wfLoadExtensionMessages( 'SecurePoll' ); |
36 | | - |
37 | | - $this->setHeaders(); |
38 | | - $wgOut->addLink( array( |
39 | | - 'rel' => 'stylesheet', |
40 | | - 'href' => "$wgScriptPath/extensions/SecurePoll/SecurePoll.css", |
41 | | - 'type' => 'text/css' |
42 | | - ) ); |
43 | | - $wgOut->addScriptFile( "$wgScriptPath/extensions/SecurePoll/SecurePoll.js" ); |
44 | | - |
45 | | - $this->request = $wgRequest; |
46 | | - |
47 | | - $paramString = strval( $paramString ); |
48 | | - if ( $paramString === '' ) { |
49 | | - $paramString = 'entry'; |
50 | | - } |
51 | | - $params = explode( '/', $paramString ); |
52 | | - $pageName = array_shift( $params ); |
53 | | - $page = $this->getSubpage( $pageName ); |
54 | | - if ( !$page ) { |
55 | | - $wgOut->addWikiMsg( 'securepoll-invalid-page', $pageName ); |
56 | | - return; |
57 | | - } |
58 | | - |
59 | | - $page->execute( $params ); |
60 | | - } |
61 | | - |
62 | | - function getSubpage( $name ) { |
63 | | - if ( !isset( self::$pages[$name] ) ) { |
64 | | - return false; |
65 | | - } |
66 | | - $className = self::$pages[$name]; |
67 | | - $page = new $className( $this ); |
68 | | - return $page; |
69 | | - } |
70 | | - |
71 | | - function getElection( $id ) { |
72 | | - $db = wfGetDB( DB_MASTER ); |
73 | | - $row = $db->selectRow( 'securepoll_elections', '*', array( 'el_entity' => $id ), __METHOD__ ); |
74 | | - if ( $row ) { |
75 | | - return SecurePoll_Election::newFromRow( $row ); |
76 | | - } else { |
77 | | - return false; |
78 | | - } |
79 | | - } |
80 | | - |
81 | | - function getEditToken() { |
82 | | - if ( !isset( $_SESSION['spToken'] ) ) { |
83 | | - $_SESSION['spToken'] = sha1( mt_rand() . mt_rand() . mt_rand() ); |
84 | | - } |
85 | | - return $_SESSION['spToken']; |
86 | | - } |
87 | | -} |
Index: trunk/extensions/SecurePoll/SecurePoll.css |
— | — | @@ -1,44 +0,0 @@ |
2 | | -.securepoll-old-vote { |
3 | | - color: #666; |
4 | | -} |
5 | | -.securepoll-struck-vote td { |
6 | | - text-decoration: line-through; |
7 | | -} |
8 | | -.securepoll-popup { |
9 | | - padding: 0.5em 1em 0.5em 1em; |
10 | | - border: outset; |
11 | | - position: absolute; |
12 | | - height: 7em; |
13 | | - width: 38em; |
14 | | - display: none; |
15 | | - background-color: #ffffdd; |
16 | | - overflow: auto; |
17 | | - z-index: 3; |
18 | | -} |
19 | | -.securepoll-confirm-button { |
20 | | - width: 16%; |
21 | | - margin-left: 16%; |
22 | | - margin-right: 16%; |
23 | | -} |
24 | | -#securepoll-strike-spinner { |
25 | | - display: none; |
26 | | - margin-left: auto; |
27 | | - margin-right: auto; |
28 | | - width: 30px; |
29 | | -} |
30 | | -.securepoll-detail-header { |
31 | | - font-weight: bold; |
32 | | - width: 20%; |
33 | | -} |
34 | | -.securepoll-trans-table { |
35 | | - width: 100%; |
36 | | -} |
37 | | -.securepoll-error-box { |
38 | | - font-size: larger; |
39 | | - border: 2px solid; |
40 | | - padding: .5em 1em; |
41 | | - margin-bottom: 2em; |
42 | | - color: #000; |
43 | | - border-color: red; |
44 | | - background-color: #fff2f2; |
45 | | -} |
Index: trunk/extensions/SecurePoll/resources/critical-32.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/SecurePoll/resources/critical-32.png |
___________________________________________________________________ |
Added: svn:mime-type |
46 | 1 | + image/png |
Added: svn:executable |
47 | 2 | + * |
Index: trunk/extensions/SecurePoll/resources/SecurePoll.css |
— | — | @@ -0,0 +1,44 @@ |
| 2 | +.securepoll-old-vote { |
| 3 | + color: #666; |
| 4 | +} |
| 5 | +.securepoll-struck-vote td { |
| 6 | + text-decoration: line-through; |
| 7 | +} |
| 8 | +.securepoll-popup { |
| 9 | + padding: 0.5em 1em 0.5em 1em; |
| 10 | + border: outset; |
| 11 | + position: absolute; |
| 12 | + height: 7em; |
| 13 | + width: 38em; |
| 14 | + display: none; |
| 15 | + background-color: #ffffdd; |
| 16 | + overflow: auto; |
| 17 | + z-index: 3; |
| 18 | +} |
| 19 | +.securepoll-confirm-button { |
| 20 | + width: 16%; |
| 21 | + margin-left: 16%; |
| 22 | + margin-right: 16%; |
| 23 | +} |
| 24 | +#securepoll-strike-spinner { |
| 25 | + display: none; |
| 26 | + margin-left: auto; |
| 27 | + margin-right: auto; |
| 28 | + width: 30px; |
| 29 | +} |
| 30 | +.securepoll-detail-header { |
| 31 | + font-weight: bold; |
| 32 | + width: 20%; |
| 33 | +} |
| 34 | +.securepoll-trans-table { |
| 35 | + width: 100%; |
| 36 | +} |
| 37 | +.securepoll-error-box { |
| 38 | + font-size: larger; |
| 39 | + border: 2px solid; |
| 40 | + padding: .5em 1em; |
| 41 | + margin-bottom: 2em; |
| 42 | + color: #000; |
| 43 | + border-color: red; |
| 44 | + background-color: #fff2f2; |
| 45 | +} |
Property changes on: trunk/extensions/SecurePoll/resources/SecurePoll.css |
___________________________________________________________________ |
Added: svn:mergeinfo |
Added: svn:eol-style |
1 | 46 | + native |
Index: trunk/extensions/SecurePoll/resources/SecurePoll.js |
— | — | @@ -0,0 +1,111 @@ |
| 2 | +function securepoll_strike_popup(e, action, id) { |
| 3 | + var pop = document.getElementById('securepoll-popup'); |
| 4 | + |
| 5 | + var e = window.event || e; |
| 6 | + if(!e) return; |
| 7 | + var target = e.target || e.srcElement; |
| 8 | + if(!target) return; |
| 9 | + |
| 10 | + if ( pop.parentNode.tagName.toLowerCase() != 'body' ) { |
| 11 | + pop = pop.parentNode.removeChild( pop ); |
| 12 | + pop = document.body.appendChild( pop ); |
| 13 | + } |
| 14 | + |
| 15 | + var left = 0; |
| 16 | + var top = 0; |
| 17 | + var containing = target; |
| 18 | + while ( containing ) { |
| 19 | + left += containing.offsetLeft; |
| 20 | + top += containing.offsetTop; |
| 21 | + containing = containing.offsetParent; |
| 22 | + } |
| 23 | + left += target.offsetWidth - 10; |
| 24 | + top += target.offsetHeight - 10; |
| 25 | + |
| 26 | + // Show the appropriate button |
| 27 | + var strikeButton = document.getElementById( 'securepoll-strike-button' ); |
| 28 | + var unstrikeButton = document.getElementById( 'securepoll-unstrike-button' ); |
| 29 | + if ( action == 'strike' ) { |
| 30 | + strikeButton.style.display = 'inline'; |
| 31 | + strikeButton.disabled = false; |
| 32 | + unstrikeButton.style.display = 'none'; |
| 33 | + unstrikeButton.disabled = true; |
| 34 | + } else { |
| 35 | + unstrikeButton.style.display = 'inline'; |
| 36 | + unstrikeButton.disabled = false; |
| 37 | + strikeButton.style.display = 'none'; |
| 38 | + strikeButton.disabled = true; |
| 39 | + } |
| 40 | + document.getElementById( 'securepoll-strike-result' ).innerHTML = ''; |
| 41 | + |
| 42 | + // Set the hidden fields for submission |
| 43 | + document.getElementById( 'securepoll-action').value = action; |
| 44 | + document.getElementById( 'securepoll-vote-id' ).value = id; |
| 45 | + |
| 46 | + // Show the popup |
| 47 | + pop.style.left = left + 'px'; |
| 48 | + pop.style.top = top + 'px'; |
| 49 | + pop.style.display = 'block'; |
| 50 | + |
| 51 | + // Focus on the reason box |
| 52 | + var reason = document.getElementById( 'securepoll-strike-reason' ); |
| 53 | + reason.focus(); |
| 54 | + reason.select(); |
| 55 | +} |
| 56 | + |
| 57 | +function securepoll_strike(action) { |
| 58 | + var popup = document.getElementById('securepoll-popup'); |
| 59 | + if(action == 'cancel') { |
| 60 | + popup.style.display = ''; |
| 61 | + return; |
| 62 | + } |
| 63 | + if ( action == 'submit' ) { |
| 64 | + action = document.getElementById( 'securepoll-action' ).value; |
| 65 | + } |
| 66 | + var id = document.getElementById( 'securepoll-vote-id' ).value; |
| 67 | + var strikeButton = document.getElementById( 'securepoll-strike-button' ); |
| 68 | + var unstrikeButton = document.getElementById( 'securepoll-unstrike-button' ); |
| 69 | + var spinner = document.getElementById( 'securepoll-strike-spinner' ); |
| 70 | + strikeButton.disabled = true; |
| 71 | + unstrikeButton.disabled = true; |
| 72 | + spinner.style.display = 'block'; |
| 73 | + var reason = document.getElementById( 'securepoll-strike-reason' ).value; |
| 74 | + |
| 75 | + var processResult = function (xhr) { |
| 76 | + spinner.style.display = 'none'; |
| 77 | + strikeButton.disabled = false; |
| 78 | + unstrikeButton.disabled = false; |
| 79 | + |
| 80 | + if ( xhr.status >= 300 || xhr.status < 200 ) { |
| 81 | + document.getElementById( 'securepoll-strike-result' ).innerHTML = xhr.responseText; |
| 82 | + return; |
| 83 | + } |
| 84 | + |
| 85 | + // Evaluate JSON result, with brackets to avoid interpretation as a code block |
| 86 | + result = eval( '(' + xhr.responseText + ')' ); |
| 87 | + if ( result.status == 'good' ) { |
| 88 | + popup.style.display = 'none'; |
| 89 | + } else { |
| 90 | + document.getElementById( 'securepoll-strike-result' ).innerHTML = result.message; |
| 91 | + } |
| 92 | + |
| 93 | + securepoll_modify_document( action, id ); |
| 94 | + }; |
| 95 | + |
| 96 | + sajax_do_call( 'wfSecurePollStrike', [ action, id, reason ], processResult ); |
| 97 | +} |
| 98 | + |
| 99 | +function securepoll_modify_document( action, voteId ) { |
| 100 | + var popupButton = document.getElementById( 'securepoll-popup-' + voteId ); |
| 101 | + var row = popupButton.parentNode.parentNode; |
| 102 | + if ( action == 'strike' ) { |
| 103 | + row.className += ' securepoll-struck-vote'; |
| 104 | + popupButton.value = securepoll_unstrike_button; |
| 105 | + } else { |
| 106 | + row.className = row.className.replace( 'securepoll-struck-vote', '' ); |
| 107 | + popupButton.value = securepoll_strike_button; |
| 108 | + } |
| 109 | + popupButton.onclick = function (event) { |
| 110 | + securepoll_strike_popup( event, action == 'strike' ? 'unstrike' : 'strike', voteId ); |
| 111 | + } |
| 112 | +} |
Property changes on: trunk/extensions/SecurePoll/resources/SecurePoll.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
Added: svn:eol-style |
1 | 113 | + native |
Index: trunk/extensions/SecurePoll/SecurePoll.sql |
— | — | @@ -1,106 +1,216 @@ |
2 | 2 | |
| 3 | +-- Generic entity ID allocation |
3 | 4 | CREATE TABLE /*_*/securepoll_entity ( |
| 5 | + -- ID |
4 | 6 | en_id int not null primary key auto_increment, |
| 7 | + |
| 8 | + -- "election", "question" or "option" |
5 | 9 | en_type varbinary(32) not null |
6 | 10 | ) /*$wgDBTableOptions*/; |
7 | 11 | |
| 12 | + |
| 13 | +-- i18n text associated with an entity |
8 | 14 | CREATE TABLE /*_*/securepoll_msgs ( |
| 15 | + -- securepoll_entity.en_id |
9 | 16 | msg_entity int not null, |
| 17 | + |
| 18 | + -- Language code |
10 | 19 | msg_lang varbinary(32) not null, |
| 20 | + |
| 21 | + -- Message key |
11 | 22 | msg_key varbinary(32) not null, |
| 23 | + |
| 24 | + -- Message text, UTF-8 encoded |
12 | 25 | msg_text mediumtext not null |
13 | 26 | ) /*$wgDBTableOptions*/; |
14 | 27 | CREATE UNIQUE INDEX /*i*/spmsg_entity ON /*_*/securepoll_msgs (msg_entity, msg_lang, msg_key); |
15 | 28 | |
| 29 | + |
| 30 | +-- key/value pairs (properties) associated with an entity |
16 | 31 | CREATE TABLE /*_*/securepoll_properties ( |
| 32 | + -- securepoll_entity.en_id |
17 | 33 | pr_entity int not null, |
| 34 | + |
| 35 | + -- Property key |
18 | 36 | pr_key varbinary(32) not null, |
| 37 | + |
| 38 | + -- Property value |
19 | 39 | pr_value mediumblob not null |
20 | 40 | ) /*$wgDBTableOptions*/; |
21 | 41 | CREATE UNIQUE INDEX /*i*/sppr_entity ON /*_*/securepoll_properties (pr_entity, pr_key); |
22 | 42 | |
| 43 | + |
| 44 | +-- List of elections (or polls, surveys, etc) |
23 | 45 | CREATE TABLE /*_*/securepoll_elections ( |
| 46 | + -- securepoll_entity.en_id |
24 | 47 | el_entity int not null primary key, |
| 48 | + |
| 49 | + -- Election title |
| 50 | + -- Only used for the election list on the entry page |
25 | 51 | el_title varchar(255) not null, |
| 52 | + |
| 53 | + -- Owner user.user_id |
| 54 | + el_owner int not null, |
| 55 | + |
| 56 | + -- Ballot type, see Ballot.php |
26 | 57 | el_ballot varchar(32) not null, |
| 58 | + |
| 59 | + -- Tally type, see Tally.php |
27 | 60 | el_tally varchar(32) not null, |
| 61 | + |
| 62 | + -- Primary (administrative) language |
| 63 | + -- This is the primary source for translations |
28 | 64 | el_primary_lang varbinary(32) not null, |
| 65 | + |
| 66 | + -- Start date, in 14-char MW format |
29 | 67 | el_start_date varbinary(14), |
| 68 | + |
| 69 | + -- End date, in 14-char MW format |
30 | 70 | el_end_date varbinary(14), |
| 71 | + |
| 72 | + -- User authorisation type, see Auth.php |
31 | 73 | el_auth_type varbinary(32) not null |
32 | 74 | ) /*$wgDBTableOptions*/; |
33 | 75 | CREATE UNIQUE INDEX /*i*/spel_title ON /*_*/securepoll_elections (el_title); |
34 | 76 | |
| 77 | + |
| 78 | +-- Questions, see Question.php |
35 | 79 | CREATE TABLE /*_*/securepoll_questions ( |
| 80 | + -- securepoll_entity.en_id |
36 | 81 | qu_entity int not null primary key, |
| 82 | + |
| 83 | + -- securepoll_elections.el_entity |
37 | 84 | qu_election int not null, |
| 85 | + |
| 86 | + -- Index determining the order the questions are shown, if shuffle is off |
38 | 87 | qu_index int not null |
39 | 88 | ) /*$wgDBTableOptions*/; |
40 | 89 | CREATE INDEX /*i*/spqu_election_index ON /*_*/securepoll_questions (qu_election, qu_index, qu_entity); |
41 | 90 | |
| 91 | + |
| 92 | +-- Options for answering a given question, see Option.php |
42 | 93 | CREATE TABLE /*_*/securepoll_options ( |
| 94 | + -- securepoll_entity.en_id |
43 | 95 | op_entity int not null primary key, |
| 96 | + -- securepoll_elections.el_entity |
44 | 97 | op_election int not null, |
| 98 | + -- securepoll_questions.qu_entity |
45 | 99 | op_question int not null |
46 | 100 | ) /*$wgDBTableOptions*/; |
47 | 101 | CREATE INDEX /*i*/spop_question ON /*_*/securepoll_options (op_question, op_entity); |
48 | 102 | |
| 103 | +-- Voter list, independent for each election |
| 104 | +-- See Voter.php |
49 | 105 | CREATE TABLE /*_*/securepoll_voters ( |
| 106 | + -- Primary key |
50 | 107 | voter_id int not null primary key auto_increment, |
| 108 | + -- securepoll_elections.el_id |
51 | 109 | voter_election int not null, |
| 110 | + -- The voter's name, as it appears on the remote site |
52 | 111 | voter_name varchar(255) binary not null, |
| 112 | + -- The auth type that created this voter |
53 | 113 | voter_type varbinary(32) not null, |
| 114 | + -- The voter's domain, should be fully-qualified |
54 | 115 | voter_domain varbinary(255) not null, |
| 116 | + -- A URL uniquely identifying the voter |
55 | 117 | voter_url blob, |
| 118 | + -- serialized properties blob |
56 | 119 | voter_properties blob |
57 | 120 | ) /*$wgDBTableOptions*/; |
58 | 121 | CREATE INDEX /*i*/spvoter_elec_name_domain ON /*_*/securepoll_voters |
59 | 122 | (voter_election, voter_name, voter_domain); |
60 | 123 | |
| 124 | +-- Votes that have been cast |
| 125 | +-- Contains a blob with answers to all questions |
61 | 126 | CREATE TABLE /*_*/securepoll_votes ( |
62 | 127 | vote_id int not null primary key auto_increment, |
| 128 | + -- securepoll_elections.el_id |
63 | 129 | vote_election int not null, |
| 130 | + -- securepoll_voters.voter_id |
64 | 131 | vote_voter int not null, |
65 | 132 | |
66 | 133 | -- Denormalised fields from the user table for efficient sorting |
| 134 | + |
| 135 | + -- securepoll_voters.voter_name |
67 | 136 | vote_voter_name varchar(255) binary not null, |
| 137 | + -- securepoll_voters.voter_domain |
68 | 138 | vote_voter_domain varbinary(32) not null, |
69 | 139 | |
70 | 140 | -- Denormalised field from the strike table |
71 | 141 | -- 1 if struck, 0 if not struck |
72 | 142 | vote_struck tinyint not null, |
73 | 143 | |
| 144 | + -- The voting record, produced and interpreted by the ballot type |
| 145 | + -- May be encrypted |
74 | 146 | vote_record blob not null, |
| 147 | + |
| 148 | + -- The IP address, in hexadecimal form (IP::toHex()) |
75 | 149 | vote_ip varbinary(32) not null, |
| 150 | + |
| 151 | + -- The X-Forwarded-For header |
76 | 152 | vote_xff varbinary(255) not null, |
| 153 | + |
| 154 | + -- The User-Agent header |
77 | 155 | vote_ua varbinary(255) not null, |
| 156 | + |
| 157 | + -- MW-format timestamp when the vote was cast |
78 | 158 | vote_timestamp varbinary(14) not null, |
| 159 | + |
| 160 | + -- 1 if the vote is current, 0 if old |
| 161 | + -- Only one vote with a given voter will have vote_current=1 |
79 | 162 | vote_current tinyint not null, |
| 163 | + |
| 164 | + -- 1 if the CSRF token matched (good), 0 for a potential hack |
80 | 165 | vote_token_match tinyint not null, |
| 166 | + |
| 167 | + -- 1 if the vote is flagged as being made by a potential sockpuppet |
| 168 | + -- Details in securepoll_cookie_match |
81 | 169 | vote_cookie_dup tinyint not null |
82 | 170 | ) /*$wgDBTableOptions*/; |
| 171 | +-- For list subpage, sorted by timestamp |
83 | 172 | CREATE INDEX /*i*/spvote_timestamp ON /*_*/securepoll_votes |
84 | 173 | (vote_election, vote_timestamp); |
| 174 | +-- For list subpage, sorted by name |
85 | 175 | CREATE INDEX /*i*/spvote_voter_name ON /*_*/securepoll_votes |
86 | 176 | (vote_election, vote_voter_name, vote_timestamp); |
| 177 | +-- For list subpage, sorted by domain |
87 | 178 | CREATE INDEX /*i*/spvote_voter_domain ON /*_*/securepoll_votes |
88 | 179 | (vote_election, vote_voter_domain, vote_timestamp); |
| 180 | +-- For list subpage, sorted by IP |
89 | 181 | CREATE INDEX /*i*/spvote_ip ON /*_*/securepoll_votes |
90 | 182 | (vote_election, vote_ip, vote_timestamp); |
91 | 183 | |
| 184 | +-- Log of admin strike actions |
92 | 185 | CREATE TABLE /*_*/securepoll_strike ( |
| 186 | + -- Primary key |
93 | 187 | st_id int not null primary key auto_increment, |
| 188 | + |
| 189 | + -- securepoll_votes.vote_id |
94 | 190 | st_vote int not null, |
| 191 | + |
| 192 | + -- Time at which the action occurred |
95 | 193 | st_timestamp varbinary(14) not null, |
| 194 | + |
| 195 | + -- "strike" or "unstrike" |
96 | 196 | st_action varbinary(32) not null, |
| 197 | + |
| 198 | + -- Explanatory reason |
97 | 199 | st_reason varchar(255) not null, |
| 200 | + |
| 201 | + -- user.user_id who did the action |
98 | 202 | st_user int not null |
99 | 203 | ) /*$wgDBTableOptions*/; |
| 204 | +-- For details subpage (strike log) |
100 | 205 | CREATE INDEX /*i*/spstrike_vote ON /*_*/securepoll_strike |
101 | 206 | (st_vote, st_timestamp); |
102 | 207 | |
| 208 | + |
| 209 | +-- Local voter qualification lists |
| 210 | +-- Currently manually populated, referenced by Auth.php |
103 | 211 | CREATE TABLE /*_*/securepoll_lists ( |
| 212 | + -- List name |
104 | 213 | li_name varbinary(255), |
| 214 | + -- user.user_id |
105 | 215 | li_member int not null |
106 | 216 | ) /*$wgDBTableOptions*/; |
107 | 217 | CREATE INDEX /*i*/splists_name ON /*_*/securepoll_lists |
— | — | @@ -108,11 +218,17 @@ |
109 | 219 | CREATE INDEX /*i*/splists_member ON /*_*/securepoll_lists |
110 | 220 | (li_member, li_name); |
111 | 221 | |
| 222 | +-- Suspicious cookie match logs |
112 | 223 | CREATE TABLE /*_*/securepoll_cookie_match ( |
| 224 | + -- Primary key |
113 | 225 | cm_id int not null primary key auto_increment, |
| 226 | + -- securepoll_elections.el_id |
114 | 227 | cm_election int not null, |
| 228 | + -- securepoll_voters.voter_id |
115 | 229 | cm_voter_1 int not null, |
| 230 | + -- securepoll_voters.voter_id |
116 | 231 | cm_voter_2 int not null, |
| 232 | + -- Timestamp at which the match was logged |
117 | 233 | cm_timestamp varbinary(14) not null |
118 | 234 | ) /*$wgDBTableOptions*/; |
119 | 235 | CREATE INDEX /*i*/spcookie_match_voter_1 ON /*_*/securepoll_cookie_match |
Index: trunk/extensions/SecurePoll/SecurePoll.i18n.php |
— | — | @@ -57,10 +57,11 @@ |
58 | 58 | 'securepoll-no-decryption-key' => 'No decryption key is configured. |
59 | 59 | Cannot decrypt.', |
60 | 60 | 'securepoll-jump' => 'Go to the voting server', |
61 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
62 | | - Your vote was invalid: $1 |
63 | | - </div>', |
| 61 | + 'securepoll-bad-ballot-submission' => 'Your vote was invalid: $1', |
64 | 62 | 'securepoll-unanswered-questions' => 'You must answer all questions.', |
| 63 | + 'securepoll-invalid-rank' => 'Invalid rank. You must give candidates a rank between 1 and 999.', |
| 64 | + 'securepoll-unranked-options' => 'Some options were not ranked. |
| 65 | +You must give all options a rank between 1 and 999.', |
65 | 66 | |
66 | 67 | # Authorisation related |
67 | 68 | 'securepoll-remote-auth-error' => 'Error fetching your account information from the server.', |
— | — | @@ -94,6 +95,7 @@ |
95 | 96 | 'securepoll-strike-reason' => 'Reason:', |
96 | 97 | 'securepoll-strike-cancel' => 'Cancel', |
97 | 98 | 'securepoll-strike-error' => 'Error performing strike/unstrike: $1', |
| 99 | + 'securepoll-strike-token-mismatch' => 'Session data lost', |
98 | 100 | 'securepoll-details-link' => 'Details', |
99 | 101 | |
100 | 102 | # Details page |
— | — | @@ -116,6 +118,8 @@ |
117 | 119 | 'securepoll-dump-not-finished' => 'Encrypted election records are only available after the finish date on $1 at $2', |
118 | 120 | 'securepoll-dump-no-urandom' => 'Cannot open /dev/urandom. |
119 | 121 | To maintain voter privacy, encrypted election records are only publically available when they can be shuffled with a secure random number stream.', |
| 122 | + 'securepoll-urandom-not-supported' => 'This server does not support cryptographic random number generation. |
| 123 | +To maintain voter privacy, encrypted election records are only publically available when they can be shuffled with a secure random number stream.', |
120 | 124 | |
121 | 125 | # Translate page |
122 | 126 | 'securepoll-translate-title' => 'Translate: $1', |
— | — | @@ -251,9 +255,7 @@ |
252 | 256 | 'securepoll-no-decryption-key' => 'لا توجد مفاتيح فك شفرة مهيئة. |
253 | 257 | لا يمكن فك الشفرة.', |
254 | 258 | 'securepoll-jump' => 'اذهب إلى خادم التصويت', |
255 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
256 | | -تصويتك ليس صحيحا: $1 |
257 | | -</div>', |
| 259 | + 'securepoll-bad-ballot-submission' => 'تصويتك ليس صحيحا: $1', |
258 | 260 | 'securepoll-unanswered-questions' => 'يجب أن تجيب على كل الأسئلة.', |
259 | 261 | 'securepoll-remote-auth-error' => 'خطأ عند جلب معلومات حسابك من الخادوم.', |
260 | 262 | 'securepoll-remote-parse-error' => 'خطأ عند تفسير رد التصريح من الخادوم.', |
— | — | @@ -844,9 +846,7 @@ |
845 | 847 | 'securepoll-no-decryption-key' => "Nid yw'r allwedd dadgryptio wedi ei ffurfweddu. |
846 | 848 | Ni ellir dadgryptio.", |
847 | 849 | 'securepoll-jump' => 'Mynd i weinydd y pleidleisio', |
848 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
849 | | -Nid oedd eich pleidlais yn ddilys: $1 |
850 | | -</div>', |
| 850 | + 'securepoll-bad-ballot-submission' => 'Nid oedd eich pleidlais yn ddilys: $1', |
851 | 851 | 'securepoll-unanswered-questions' => 'Rhaid ateb pob cwestiwn.', |
852 | 852 | 'securepoll-remote-auth-error' => "Cafwyd gwall wrth nôl gwybodaeth eich cyfrif o'r gweinydd.", |
853 | 853 | 'securepoll-remote-parse-error' => "Cafwyd gwall wrth ddehongli ymateb y gweinydd i'r cais awdurdodi.", |
— | — | @@ -961,9 +961,7 @@ |
962 | 962 | 'securepoll-no-decryption-key' => 'Ingen dekrypteringsnøgle opsat. |
963 | 963 | Kan ikke dekryptere.', |
964 | 964 | 'securepoll-jump' => 'Gå til stemmeserveren', |
965 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
966 | | -Din stemme var ugyldig: $1 |
967 | | -</div>', |
| 965 | + 'securepoll-bad-ballot-submission' => 'Din stemme var ugyldig: $1', |
968 | 966 | 'securepoll-unanswered-questions' => 'Du skal besvare alle spørgsmålene.', |
969 | 967 | 'securepoll-remote-auth-error' => 'Der opstod en fejl under hentning af dine kontoinformationer fra serveren.', |
970 | 968 | 'securepoll-remote-parse-error' => 'Der opstod en fejl under læsning af autorisationssvarene fra serveren.', |
— | — | @@ -1343,9 +1341,7 @@ |
1344 | 1342 | 'securepoll-no-decryption-key' => 'Δεν έχει ρυθμιστεί κλειδί αποκρυπτογράφησης. |
1345 | 1343 | Δεν είναι δυνατή η αποκρυπτογράφηση.', |
1346 | 1344 | 'securepoll-jump' => 'Μετάβαση στον διακομιστή ψηφοφορίας', |
1347 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
1348 | | -Η ψήφος σας ήταν άκυρη: $1 |
1349 | | -</div>', |
| 1345 | + 'securepoll-bad-ballot-submission' => 'Η ψήφος σας ήταν άκυρη: $1', |
1350 | 1346 | 'securepoll-unanswered-questions' => 'Πρέπει να απαντήσεις σε όλες τις ερωτήσεις.', |
1351 | 1347 | 'securepoll-remote-auth-error' => 'Σφάλμα κατά την ανάκτηση των πληροφοριών για τον λογαριασμό σας από τον διακομιστή.', |
1352 | 1348 | 'securepoll-remote-parse-error' => 'Σφάλμα στην ερμηνεία της απάντησης για άδεια πρόβασης από τον διακομιστή.', |
— | — | @@ -1458,9 +1454,7 @@ |
1459 | 1455 | 'securepoll-no-decryption-key' => 'Neniu malĉifra ŝlosilo estas konfigurita. |
1460 | 1456 | Ne eblas malĉifri.', |
1461 | 1457 | 'securepoll-jump' => 'Iri al la voĉdona servilo', |
1462 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
1463 | | -Via voĉdono estis malvalida: $1 |
1464 | | -</div>', |
| 1458 | + 'securepoll-bad-ballot-submission' => 'Via voĉdono estis malvalida: $1', |
1465 | 1459 | 'securepoll-unanswered-questions' => 'Vi devas respondi al ĉiuj demandoj.', |
1466 | 1460 | 'securepoll-remote-auth-error' => 'Eraro akirante vian kontinformon de la servilo.', |
1467 | 1461 | 'securepoll-remote-parse-error' => 'Eraro interpretante la aŭtoritadan respondon de la servilo.', |
— | — | @@ -1759,9 +1753,7 @@ |
1760 | 1754 | 'securepoll-no-decryption-key' => 'Salauksen purkuavainta ei ole asetettu. |
1761 | 1755 | Salausta ei voi purkaa.', |
1762 | 1756 | 'securepoll-jump' => 'Siirry äänestyspalvelimelle.', |
1763 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
1764 | | -Äänesi oli epäkelpo: $1 |
1765 | | -</div>', |
| 1757 | + 'securepoll-bad-ballot-submission' => 'Äänesi oli epäkelpo: $1', |
1766 | 1758 | 'securepoll-unanswered-questions' => 'Sinun täytyy vastata kaikkiin kysymyksiin.', |
1767 | 1759 | 'securepoll-remote-auth-error' => 'Virhe hakiessa käyttäjätilisi tietoja palvelimelta.', |
1768 | 1760 | 'securepoll-remote-parse-error' => 'Virhe tulkittaessa lupavastausta palvelimelta.', |
— | — | @@ -2366,9 +2358,7 @@ |
2367 | 2359 | 'securepoll-no-decryption-key' => 'Dekripcijski ključ nije konfiguriran. |
2368 | 2360 | Dekripcija nije moguća.', |
2369 | 2361 | 'securepoll-jump' => 'Idi na poslužitelj za glasovanje', |
2370 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
2371 | | -Vaš glas je bio nevažeći: $1 |
2372 | | -</div>', |
| 2362 | + 'securepoll-bad-ballot-submission' => 'Vaš glas je bio nevažeći: $1', |
2373 | 2363 | 'securepoll-unanswered-questions' => 'Morate odgovoriti na sva pitanja.', |
2374 | 2364 | 'securepoll-remote-auth-error' => 'Pogreška pri dobavljanje informacije o Vašem računu s poslužitelja.', |
2375 | 2365 | 'securepoll-remote-parse-error' => 'Pogreška pri tumačenju autorizacijskog odgovora s poslužitelja.', |
— | — | @@ -2597,9 +2587,7 @@ |
2598 | 2588 | 'securepoll-no-decryption-key' => 'Nincs visszafejtő kulcs beállítva. |
2599 | 2589 | Nem lehet visszafejteni.', |
2600 | 2590 | 'securepoll-jump' => 'Irány a szavazás-szerverre', |
2601 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
2602 | | -A szavazatod érvénytelen volt: $1 |
2603 | | -</div>', |
| 2591 | + 'securepoll-bad-ballot-submission' => 'A szavazatod érvénytelen volt: $1', |
2604 | 2592 | 'securepoll-unanswered-questions' => 'Minden kérdésre válaszolnod kell.', |
2605 | 2593 | 'securepoll-remote-auth-error' => 'Nem sikerült lekérdezni a felhasználói fiókod adatait a szerverről.', |
2606 | 2594 | 'securepoll-remote-parse-error' => 'Nem sikerült értelmezni a szerver autorizációs válaszát.', |
— | — | @@ -2821,9 +2809,7 @@ |
2822 | 2810 | 'securepoll-no-decryption-key' => 'Kunci dekripsi belum dikonfigurasikan. |
2823 | 2811 | Tidak dapat melakukan dekripsi.', |
2824 | 2812 | 'securepoll-jump' => 'Pergi ke server pemungutan suara', |
2825 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
2826 | | -Suara Anda tidak valid: $1 |
2827 | | -</div>', |
| 2813 | + 'securepoll-bad-ballot-submission' => 'Suara Anda tidak valid: $1', |
2828 | 2814 | 'securepoll-unanswered-questions' => 'Anda harus menjawab semua pertanyaan.', |
2829 | 2815 | 'securepoll-remote-auth-error' => 'Terjadi kesalahan ketika menarik informasi akun Anda dari server.', |
2830 | 2816 | 'securepoll-remote-parse-error' => 'Terjadi kesalahan interpretasi atas respons otorisasi dari server.', |
— | — | @@ -2949,9 +2935,7 @@ |
2950 | 2936 | 'securepoll-no-decryption-key' => 'Nessuna chiave di decrittazione è configurata. |
2951 | 2937 | Impossibile decifrare.', |
2952 | 2938 | 'securepoll-jump' => 'Vai al server della votazione', |
2953 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
2954 | | -Il tuo voto non era valido: $1 |
2955 | | -</div>', |
| 2939 | + 'securepoll-bad-ballot-submission' => 'Il tuo voto non era valido: $1', |
2956 | 2940 | 'securepoll-unanswered-questions' => 'È necessario rispondere a tutte le domande.', |
2957 | 2941 | 'securepoll-remote-auth-error' => 'Errore durante il recupero delle informazioni sul tuo account dal server.', |
2958 | 2942 | 'securepoll-remote-parse-error' => "Errore nell'interpretare la risposta di autorizzazione dal server.", |
— | — | @@ -3165,9 +3149,7 @@ |
3166 | 3150 | 'securepoll-no-decryption-key' => '암호 해독 키가 설정되지 않았습니다. |
3167 | 3151 | 암호를 해독할 수 없습니다.', |
3168 | 3152 | 'securepoll-jump' => '선거 서버로 이동하기', |
3169 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
3170 | | -당신의 투표가 무효화되었습니다: $1 |
3171 | | -</div>', |
| 3153 | + 'securepoll-bad-ballot-submission' => '당신의 투표가 무효화되었습니다: $1', |
3172 | 3154 | 'securepoll-unanswered-questions' => '모든 질문에 답을 하셔야 합니다.', |
3173 | 3155 | 'securepoll-remote-auth-error' => '귀하의 계정 정보를 불러오는 중에 오류가 발생하였습니다.', |
3174 | 3156 | 'securepoll-remote-parse-error' => '서버로부터 권한 응답에 따른 해석 오류가 발생', |
— | — | @@ -3388,9 +3370,7 @@ |
3389 | 3371 | 'securepoll-no-decryption-key' => 'Et ass keen Ëntschlësungsschlëssel agestallt. |
3390 | 3372 | Ëntschlësselung onméiglech.', |
3391 | 3373 | 'securepoll-jump' => 'Op den Ofstëmmungs-Server goen', |
3392 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
3393 | | -Är Stëmm ass net valabel: $1 |
3394 | | -</div>', |
| 3374 | + 'securepoll-bad-ballot-submission' => 'Är Stëmm ass net valabel: $1', |
3395 | 3375 | 'securepoll-unanswered-questions' => 'Dir musst all Froe beäntwerten', |
3396 | 3376 | 'securepoll-remote-auth-error' => 'Feeler beim Ofruf vun Äre Benotzerkontinformatioune vum Server.', |
3397 | 3377 | 'securepoll-remote-parse-error' => 'Feeler beim Interpretéiere vun der Autorisatioun déi de Server geschéckt huet.', |
— | — | @@ -3501,9 +3481,7 @@ |
3502 | 3482 | 'securepoll-no-decryption-key' => "d'r Is geine decryptiesjleutel ingesjteld. |
3503 | 3483 | Decodere is neet meugelik.", |
3504 | 3484 | 'securepoll-jump' => 'Gank nao de sjtömserver', |
3505 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
3506 | | -Dien sjtöm is óngeldig: $1 |
3507 | | -</div>', |
| 3485 | + 'securepoll-bad-ballot-submission' => 'Dien sjtöm is óngeldig: $1', |
3508 | 3486 | 'securepoll-unanswered-questions' => 'Doe mós alle vraoge beantjwaorde.', |
3509 | 3487 | 'securepoll-remote-auth-error' => "d'r Is 'n fout opgetraoje bie 't ophaole van dien gebroekersinformatie van de server.", |
3510 | 3488 | 'securepoll-remote-parse-error' => "d'r Is 'n fout opgetraoje bie 't interpretere van 't antjwaord van de server.", |
— | — | @@ -3608,9 +3586,7 @@ |
3609 | 3587 | 'securepoll-no-decryption-key' => 'Nėra sukonfigūruoto atkodavimo rakto. |
3610 | 3588 | Negalima iššifruoti.', |
3611 | 3589 | 'securepoll-jump' => 'Eiti į balsavimo serverį', |
3612 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
3613 | | -Jūsų balsas netinkamas: $1 |
3614 | | -</div>', |
| 3590 | + 'securepoll-bad-ballot-submission' => 'Jūsų balsas netinkamas: $1', |
3615 | 3591 | 'securepoll-unanswered-questions' => 'Turite atsakyti į visus klausimus.', |
3616 | 3592 | 'securepoll-remote-auth-error' => 'Įvyko klaida pristatant jūsų sąskaitos informaciją iš serverio.', |
3617 | 3593 | 'securepoll-remote-parse-error' => 'Klaida interpretuojant leidimo atsakymą iš serverio.', |
— | — | @@ -3734,9 +3710,7 @@ |
3735 | 3711 | 'securepoll-no-decryption-key' => 'Tiada kunci penyahsulitan dibentuk. |
3736 | 3712 | Tidak dapat menyahsulit.', |
3737 | 3713 | 'securepoll-jump' => 'Pergi ke pelayan undian', |
3738 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
3739 | | -Undi anda tak sah: $1 |
3740 | | -</div>', |
| 3714 | + 'securepoll-bad-ballot-submission' => 'Undi anda tak sah: $1', |
3741 | 3715 | 'securepoll-unanswered-questions' => 'Anda perlu jawab kesemua soalan.', |
3742 | 3716 | 'securepoll-remote-auth-error' => 'Ralat dalam mengambil maklumat akaun anda dari pelayan.', |
3743 | 3717 | 'securepoll-remote-parse-error' => 'Ralat menafsirkan jawapan kebenaran dari pelayan.', |
— | — | @@ -3849,9 +3823,7 @@ |
3850 | 3824 | 'securepoll-no-decryption-key' => "L-ebda ċavetta ta' dekritazzjoni ma ġiet konfigurata. |
3851 | 3825 | Impossibbli li tiġi deċifrata.", |
3852 | 3826 | 'securepoll-jump' => 'Mur fis-server tal-votazzjoni', |
3853 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
3854 | | -Il-vot tiegħek kien invalidu: $1 |
3855 | | -</div>', |
| 3827 | + 'securepoll-bad-ballot-submission' => 'Il-vot tiegħek kien invalidu: $1', |
3856 | 3828 | 'securepoll-unanswered-questions' => 'Trid tirrispondi kull mistoqsija.', |
3857 | 3829 | 'securepoll-remote-auth-error' => 'Żball waqt ir-ripristinaġġ mis-server tal-informazzjoni fuq il-kont tiegħek.', |
3858 | 3830 | 'securepoll-remote-parse-error' => "Żball fl-interpretazzjoni mis-server tar-risposta ta' awtorizzazzjoni.", |
— | — | @@ -3964,9 +3936,7 @@ |
3965 | 3937 | 'securepoll-no-decryption-key' => 'Keen Opslötel-Slötel instellt. |
3966 | 3938 | Opslöteln geiht nich.', |
3967 | 3939 | 'securepoll-jump' => 'Na’n Afstimmserver gahn', |
3968 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
3969 | | -Dien Stimm weer ungüllig: $1 |
3970 | | -</div>', |
| 3940 | + 'securepoll-bad-ballot-submission' => 'Dien Stimm weer ungüllig: $1', |
3971 | 3941 | 'securepoll-unanswered-questions' => 'Du musst all Fragen antern.', |
3972 | 3942 | 'securepoll-remote-auth-error' => 'Fehler bi’t Afropen vun dien Brukerkonteninfos vun’n Server.', |
3973 | 3943 | 'securepoll-remote-parse-error' => 'Fehler bi’t Interpreteren vun de Antwoord vun’n Server to de Rechten.', |
— | — | @@ -4208,9 +4178,7 @@ |
4209 | 4179 | 'securepoll-no-decryption-key' => 'Ingen dekrypteringsnøkkel er sett opp. |
4210 | 4180 | Kan ikkje dekryptera.', |
4211 | 4181 | 'securepoll-jump' => 'Gå til stemmetenaren', |
4212 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
4213 | | -Di stemme var ugyldig: $1 |
4214 | | -</div>', |
| 4182 | + 'securepoll-bad-ballot-submission' => 'Di stemme var ugyldig: $1', |
4215 | 4183 | 'securepoll-unanswered-questions' => 'Du må svara på alle spørsmåla.', |
4216 | 4184 | 'securepoll-remote-auth-error' => 'Feil oppstod ved henting av kontoinformasjonen din frå filtenaren.', |
4217 | 4185 | 'securepoll-remote-parse-error' => 'Feil oppsto i samband med tolking av autorisasjonssvar frå tenaren', |
— | — | @@ -4319,9 +4287,7 @@ |
4320 | 4288 | 'securepoll-no-decryption-key' => 'Ingen dekrypteringsnøkkel er konfigurert. |
4321 | 4289 | Kan ikke dekryptere.', |
4322 | 4290 | 'securepoll-jump' => 'Gå til stemmetjeneren', |
4323 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
4324 | | -Din stemme var ugyldig: $1 |
4325 | | -</div>', |
| 4291 | + 'securepoll-bad-ballot-submission' => 'Din stemme var ugyldig: $1', |
4326 | 4292 | 'securepoll-unanswered-questions' => 'Du må besvare alle spørsmålene.', |
4327 | 4293 | 'securepoll-remote-auth-error' => 'Feil oppsto ved henting av din kontoinformasjon fra tjeneren.', |
4328 | 4294 | 'securepoll-remote-parse-error' => 'Feil oppsto ved tolkning av autorisasjonssvar fra tjeneren.', |
— | — | @@ -4547,9 +4513,7 @@ |
4548 | 4514 | 'securepoll-no-decryption-key' => 'No tin un klave deskriptivó konfigurá. |
4549 | 4515 | No por decrypt.', |
4550 | 4516 | 'securepoll-jump' => 'Bai na e server di votashon', |
4551 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
4552 | | -Bo voto no ta balido: $1 |
4553 | | -</div>', |
| 4517 | + 'securepoll-bad-ballot-submission' => 'Bo voto no ta balido: $1', |
4554 | 4518 | 'securepoll-unanswered-questions' => 'Bo mester kontestá tur e preguntanan.', |
4555 | 4519 | 'securepoll-remote-auth-error' => 'Tabatin problema na ora di buska informashonnan tokante di bo kuenta riba e server.', |
4556 | 4520 | 'securepoll-remote-parse-error' => 'Tabatin problema na ora di interpretá e derechinan for di riba e server.', |
— | — | @@ -4678,9 +4642,7 @@ |
4679 | 4643 | 'securepoll-no-decryption-key' => 'Klucz odszyfrowujący nie został skonfigurowany. |
4680 | 4644 | Odszyfrowanie nie jest możliwe.', |
4681 | 4645 | 'securepoll-jump' => 'Przejdź do serwera obsługującego głosowanie', |
4682 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
4683 | | -Twój głos był nieważny – $1 |
4684 | | -</div>', |
| 4646 | + 'securepoll-bad-ballot-submission' => 'Twój głos był nieważny – $1', |
4685 | 4647 | 'securepoll-unanswered-questions' => 'Musisz odpowiedzieć na wszystkie pytania.', |
4686 | 4648 | 'securepoll-remote-auth-error' => 'Wystąpił błąd podczas pobierania informacji z serwera o Twoim koncie.', |
4687 | 4649 | 'securepoll-remote-parse-error' => 'Wystąpił błąd interpretacji odpowiedzi autoryzującej z serwera.', |
— | — | @@ -4793,9 +4755,7 @@ |
4794 | 4756 | 'securepoll-no-decryption-key' => 'Pa gnun-e ciav ëd decifrassion a son configurà. |
4795 | 4757 | As peul pa decifré.', |
4796 | 4758 | 'securepoll-jump' => 'Va al server ëd la votassion', |
4797 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
4798 | | -Tò vot a l\'era pa vàlid: $1 |
4799 | | -</div>', |
| 4759 | + 'securepoll-bad-ballot-submission' => 'Tò vot a l\'era pa vàlid: $1', |
4800 | 4760 | 'securepoll-unanswered-questions' => 'It deuve arsponde a tute le custion.', |
4801 | 4761 | 'securepoll-remote-auth-error' => 'Eror an lesend le anformassion ëd tò cont dal server.', |
4802 | 4762 | 'securepoll-remote-parse-error' => "Eror an antërpretand l'arspòsta d'autorisassion dal server.", |
— | — | @@ -4910,9 +4870,7 @@ |
4911 | 4871 | 'securepoll-no-decryption-key' => 'Nenhuma chave de descodificação está configurada. |
4912 | 4872 | Não é possível descodificar.', |
4913 | 4873 | 'securepoll-jump' => 'Ir para o servidor de votação', |
4914 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
4915 | | -O seu voto foi inválido: $1 |
4916 | | -</div>', |
| 4874 | + 'securepoll-bad-ballot-submission' => 'O seu voto foi inválido: $1', |
4917 | 4875 | 'securepoll-unanswered-questions' => 'Você deve responder todas as perguntas.', |
4918 | 4876 | 'securepoll-remote-auth-error' => 'Erro ao buscar as informações da sua conta a partir do servidor.', |
4919 | 4877 | 'securepoll-remote-parse-error' => 'Erro ao interpretar a resposta de autorização do servidor.', |
— | — | @@ -5027,9 +4985,7 @@ |
5028 | 4986 | 'securepoll-no-decryption-key' => 'Nenhuma chave de descriptografia está configurada. |
5029 | 4987 | Não foi possível descriptografar.', |
5030 | 4988 | 'securepoll-jump' => 'Ir para o servidor de votação', |
5031 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
5032 | | -Seu voto foi inválido: $1 |
5033 | | -</div>', |
| 4989 | + 'securepoll-bad-ballot-submission' => 'Seu voto foi inválido: $1', |
5034 | 4990 | 'securepoll-unanswered-questions' => 'Você deve responder todas as questões.', |
5035 | 4991 | 'securepoll-remote-auth-error' => 'Erro ao tentar obter suas informações de conta do servidor.', |
5036 | 4992 | 'securepoll-remote-parse-error' => 'Erro ao interpretar a resposta de autorização do servidor.', |
— | — | @@ -5299,9 +5255,7 @@ |
5300 | 5256 | 'securepoll-no-decryption-key' => 'Расшифровка күлүүһэ настройкаламматах. |
5301 | 5257 | Расшифровкалыыр табыллыбат.', |
5302 | 5258 | 'securepoll-jump' => 'Куоластааһын сиэрбэригэр көһүү', |
5303 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
5304 | | -Эн куолаһыҥ ааҕыллыбат: $1 |
5305 | | -</div>', |
| 5259 | + 'securepoll-bad-ballot-submission' => 'Эн куолаһыҥ ааҕыллыбат: $1', |
5306 | 5260 | 'securepoll-unanswered-questions' => 'Бары ыйытыыларга хоруйдуохтааххын.', |
5307 | 5261 | 'securepoll-remote-auth-error' => 'Аат-суол туһунан сибидиэнньэлэри сиэрбэртэн ылыыга алҕас таҕыста.', |
5308 | 5262 | 'securepoll-remote-parse-error' => 'Сиэрбэртэн авторизацияны сыыһа көрүү буолбутун туһунан хоруй кэллэ.', |
— | — | @@ -5545,9 +5499,7 @@ |
5546 | 5500 | 'securepoll-no-decryption-key' => 'Ingen dekrypteringsnyckel är konfigurerad. |
5547 | 5501 | Kan inte dekryptera.', |
5548 | 5502 | 'securepoll-jump' => 'Gå till röstnings-servern.', |
5549 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
5550 | | -Din röst var ogiltig: $1 |
5551 | | -</div>', |
| 5503 | + 'securepoll-bad-ballot-submission' => 'Din röst var ogiltig: $1', |
5552 | 5504 | 'securepoll-unanswered-questions' => 'Du måste svara på alla frågor.', |
5553 | 5505 | 'securepoll-remote-auth-error' => 'Fel uppstod vid hämtning av din kontoinformation från servern.', |
5554 | 5506 | 'securepoll-remote-parse-error' => 'Fel uppstod vid tolkning av auktorisationssvar från servern.', |
— | — | @@ -5740,9 +5692,7 @@ |
5741 | 5693 | 'securepoll-no-decryption-key' => 'Walang nakaayos na susing pangtanggal ng kodigo. |
5742 | 5694 | Hindi matanggal ang kodigo.', |
5743 | 5695 | 'securepoll-jump' => 'Pumunta sa tagapaghain ng pagboto', |
5744 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
5745 | | -Hindi tinanggap ang boto mo: $1 |
5746 | | -</div>', |
| 5696 | + 'securepoll-bad-ballot-submission' => 'Hindi tinanggap ang boto mo: $1', |
5747 | 5697 | 'securepoll-unanswered-questions' => 'Dapat mong sagutin ang lahat ng mga katanungan.', |
5748 | 5698 | 'securepoll-remote-auth-error' => 'Kamalian sa pagpulot ng kabatiran ng akawnt mo mula sa tagapaghain.', |
5749 | 5699 | 'securepoll-remote-parse-error' => 'Kamalian sa pagpapaliwanag ng tugon ng pagpapahintulot mula sa tagapaghain.', |
— | — | @@ -5968,9 +5918,7 @@ |
5969 | 5919 | 'securepoll-no-decryption-key' => 'Не налаштований ключ розшифрування. |
5970 | 5920 | Не в змозі розшифрувати.', |
5971 | 5921 | 'securepoll-jump' => 'Перейти на сервер голосувань', |
5972 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
5973 | | -Ваш голос не дійсний: $1 |
5974 | | -</div>', |
| 5922 | + 'securepoll-bad-ballot-submission' => 'Ваш голос не дійсний: $1', |
5975 | 5923 | 'securepoll-unanswered-questions' => 'Ви повинні відповісти на всі запитання.', |
5976 | 5924 | 'securepoll-remote-auth-error' => 'Помилка отримання інформації з сервера про ваш обліковий запис.', |
5977 | 5925 | 'securepoll-remote-parse-error' => 'Помилка інтерпретації відповіді від авторизації з сервера.', |
— | — | @@ -6071,9 +6019,7 @@ |
6072 | 6020 | 'securepoll-no-decryption-key' => 'No xe stà configurà nissuna ciave de decritassion. |
6073 | 6021 | No se pole decritar.', |
6074 | 6022 | 'securepoll-jump' => 'Và al server de ła votasion', |
6075 | | - 'securepoll-bad-ballot-submission' => '<div class="securepoll-error-box"> |
6076 | | -El to voto no\'l xe mia vàłido: $1 |
6077 | | -</div>', |
| 6023 | + 'securepoll-bad-ballot-submission' => 'El to voto no\'l xe mia vàłido: $1', |
6078 | 6024 | 'securepoll-unanswered-questions' => 'Ti gà da rispóndar a tute le domande.', |
6079 | 6025 | 'securepoll-remote-auth-error' => 'Eròr durante el recupero de le informassion su la to utensa dal server.', |
6080 | 6026 | 'securepoll-remote-parse-error' => 'Se gà verificà un eròr interpretando la risposta de autorixassion dal server.', |
Index: trunk/extensions/SecurePoll/SecurePoll.php |
— | — | @@ -1,11 +1,8 @@ |
2 | 2 | <?php |
3 | 3 | /** |
4 | | - * Wikimedia Foundation Board of Trustees Election |
5 | | - * |
6 | 4 | * @file |
7 | 5 | * @ingroup Extensions |
8 | 6 | * @author Tim Starling <tstarling@wikimedia.org> |
9 | | - * @author Kwan Ting Chan |
10 | 7 | * @link http://www.mediawiki.org/wiki/Extension:SecurePoll Documentation |
11 | 8 | */ |
12 | 9 | |
— | — | @@ -18,7 +15,7 @@ |
19 | 16 | $wgExtensionCredits['other'][] = array( |
20 | 17 | 'path' => __FILE__, |
21 | 18 | 'name' => 'SecurePoll', |
22 | | - 'author' => array( 'Tim Starling', 'Kwan Ting Chan', 'others' ), |
| 19 | + 'author' => array( 'Tim Starling', 'others' ), |
23 | 20 | 'url' => 'http://www.mediawiki.org/wiki/Extension:SecurePoll', |
24 | 21 | 'svn-date' => '$LastChangedDate$', |
25 | 22 | 'svn-revision' => '$LastChangedRevision$', |
— | — | @@ -50,14 +47,15 @@ |
51 | 48 | $wgExtensionMessagesFiles['SecurePoll'] = "$dir/SecurePoll.i18n.php"; |
52 | 49 | $wgExtensionAliasesFiles['SecurePoll'] = "$dir/SecurePoll.alias.php"; |
53 | 50 | |
54 | | -$wgAutoloadClasses['SecurePollPage'] = "$dir/SecurePoll_body.php"; |
55 | | -$wgSpecialPages['SecurePoll'] = 'SecurePollPage'; |
| 51 | +$wgSpecialPages['SecurePoll'] = 'SecurePoll_BasePage'; |
56 | 52 | |
57 | 53 | $wgAutoloadClasses = $wgAutoloadClasses + array( |
| 54 | + 'SecurePoll' => "$dir/includes/Base.php", |
58 | 55 | 'SecurePoll_Auth' => "$dir/includes/Auth.php", |
59 | 56 | 'SecurePoll_LocalAuth' => "$dir/includes/Auth.php", |
60 | 57 | 'SecurePoll_RemoteMWAuth' => "$dir/includes/Auth.php", |
61 | 58 | 'SecurePoll_Ballot' => "$dir/includes/Ballot.php", |
| 59 | + 'SecurePoll_BasePage' => "$dir/includes/Base.php", |
62 | 60 | 'SecurePoll_ChooseBallot' => "$dir/includes/Ballot.php", |
63 | 61 | 'SecurePoll_PreferentialBallot' => "$dir/includes/Ballot.php", |
64 | 62 | 'SecurePoll_Crypt' => "$dir/includes/Crypt.php", |
— | — | @@ -73,6 +71,7 @@ |
74 | 72 | 'SecurePoll_Option' => "$dir/includes/Option.php", |
75 | 73 | 'SecurePoll_Page' => "$dir/includes/Page.php", |
76 | 74 | 'SecurePoll_Question' => "$dir/includes/Question.php", |
| 75 | + 'SecurePoll_Random' => "$dir/includes/Random.php", |
77 | 76 | 'SecurePoll_Tallier' => "$dir/includes/Tallier.php", |
78 | 77 | 'SecurePoll_PluralityTallier' => "$dir/includes/Tallier.php", |
79 | 78 | 'SecurePoll_TallyPage' => "$dir/includes/TallyPage.php", |
Index: trunk/extensions/SecurePoll/includes/Voter.php |
— | — | @@ -158,7 +158,7 @@ |
159 | 159 | } |
160 | 160 | } |
161 | 161 | } else { |
162 | | - setcookie( $cookieName, $this->getId(), time() + 86400*3 ); |
| 162 | + setcookie( $cookieName, $this->getId(), time() + 86400*30 ); |
163 | 163 | } |
164 | 164 | } |
165 | 165 | |
Index: trunk/extensions/SecurePoll/includes/DumpPage.php |
— | — | @@ -4,6 +4,8 @@ |
5 | 5 | * Special:SecurePoll subpage for exporting encrypted election records. |
6 | 6 | */ |
7 | 7 | class SecurePoll_DumpPage extends SecurePoll_Page { |
| 8 | + var $headersSent; |
| 9 | + |
8 | 10 | /** |
9 | 11 | * Execute the subpage. |
10 | 12 | * @param $params array Array of subpage parameters. |
— | — | @@ -17,7 +19,7 @@ |
18 | 20 | } |
19 | 21 | |
20 | 22 | $electionId = intval( $params[0] ); |
21 | | - $this->election = $this->parent->getElection( $electionId ); |
| 23 | + $this->election = SecurePoll::getElection( $electionId ); |
22 | 24 | if ( !$this->election ) { |
23 | 25 | $wgOut->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
24 | 26 | return; |
— | — | @@ -40,89 +42,36 @@ |
41 | 43 | return; |
42 | 44 | } |
43 | 45 | |
44 | | - if ( !$this->openRandom() ) { |
45 | | - $wgOut->addWikiMsg( 'securepoll-dump-no-urandom' ); |
| 46 | + $this->headersSent = false; |
| 47 | + $status = $this->election->dumpVotesToCallback( array( $this, 'dumpVote' ) ); |
| 48 | + if ( !$status->isOK() && !$this->headersSent ) { |
| 49 | + $wgOut->addWikiText( $status->getWikiText() ); |
46 | 50 | return; |
47 | 51 | } |
48 | | - |
49 | | - $wgOut->disable(); |
50 | | - header( 'Content-Type: text/plain' ); |
51 | | - $filename = urlencode( "SecurePoll-$electionId-" . wfTimestampNow() ); |
52 | | - header( "Content-Disposition: attachment; filename=$filename" ); |
53 | | - $db = wfGetDB( DB_SLAVE ); |
54 | | - $res = $db->select( |
55 | | - 'securepoll_votes', |
56 | | - array( 'vote_id', 'vote_record' ), |
57 | | - array( |
58 | | - 'vote_election' => $electionId, |
59 | | - 'vote_current' => 1, |
60 | | - 'vote_struck' => 0 |
61 | | - ), |
62 | | - __METHOD__ |
63 | | - ); |
64 | | - |
65 | | - $order = $this->shuffle( range( 0, $res->numRows() - 1 ) ); |
66 | | - foreach ( $order as $i ) { |
67 | | - $res->seek( $i ); |
68 | | - echo $res->fetchObject()->vote_record . "\n\n"; |
| 52 | + if ( !$this->headersSent ) { |
| 53 | + $this->sendHeaders(); |
69 | 54 | } |
70 | | - $this->closeRandom(); |
| 55 | + echo "</election>\n</SecurePoll>\n"; |
71 | 56 | } |
72 | 57 | |
73 | | - /** |
74 | | - * Open the /dev/urandom device |
75 | | - * @return bool success |
76 | | - */ |
77 | | - function openRandom() { |
78 | | - if ( wfIsWindows() ) { |
79 | | - return false; |
| 58 | + function dumpVote( $election, $row ) { |
| 59 | + if ( !$this->headersSent ) { |
| 60 | + $this->sendHeaders(); |
80 | 61 | } |
81 | | - $this->urandom = fopen( '/dev/urandom', 'rb' ); |
82 | | - if ( !$this->urandom ) { |
83 | | - return false; |
84 | | - } |
85 | | - return true; |
| 62 | + echo "<vote>" . $row->vote_record . "</vote>\n"; |
86 | 63 | } |
87 | 64 | |
88 | | - /** |
89 | | - * Close the urandom device |
90 | | - */ |
91 | | - function closeRandom() { |
92 | | - fclose( $this->urandom ); |
93 | | - } |
| 65 | + function sendHeaders() { |
| 66 | + global $wgOut; |
94 | 67 | |
95 | | - /** |
96 | | - * Get a random integer between 0 and ($maxp1 - 1) |
97 | | - */ |
98 | | - function random( $maxp1 ) { |
99 | | - $numBytes = ceil( strlen( base_convert( $maxp1, 10, 16 ) ) / 2 ); |
100 | | - if ( $numBytes == 0 ) { |
101 | | - return 0; |
102 | | - } |
103 | | - $data = fread( $this->urandom, $numBytes ); |
104 | | - if ( strlen( $data ) != $numBytes ) { |
105 | | - throw new MWException( __METHOD__.': not enough bytes' ); |
106 | | - } |
107 | | - $x = 0; |
108 | | - for ( $i = 0; $i < $numBytes; $i++ ) { |
109 | | - $x *= 256; |
110 | | - $x += ord( substr( $data, $i, 1 ) ); |
111 | | - } |
112 | | - return $x % $maxp1; |
| 68 | + $this->headersSent = true; |
| 69 | + $wgOut->disable(); |
| 70 | + header( 'Content-Type: text/plain' ); |
| 71 | + $electionId = $this->election->getId(); |
| 72 | + $filename = urlencode( "SecurePoll-$electionId-" . wfTimestampNow() ); |
| 73 | + header( "Content-Disposition: attachment; filename=$filename" ); |
| 74 | + echo "<SecurePoll>\n<election>\n" . |
| 75 | + $this->election->getConfXml(); |
| 76 | + SecurePoll_Entity::setLanguages( array( $this->election->getLanguage() ) ); |
113 | 77 | } |
114 | | - |
115 | | - /** |
116 | | - * Works like shuffle() except more secure. Returns the new array instead |
117 | | - * of modifying it. The algorithm is the Knuth/Durstenfeld kind. |
118 | | - */ |
119 | | - function shuffle( $a ) { |
120 | | - $a = array_values( $a ); |
121 | | - for ( $i = count( $a ) - 1; $i >= 0; $i-- ) { |
122 | | - $target = $this->random( $i + 1 ); |
123 | | - $tmp = $a[$i]; |
124 | | - $a[$i] = $a[$target]; |
125 | | - $a[$target] = $tmp; |
126 | | - } |
127 | | - return $a; |
128 | | - } |
129 | 78 | } |
Index: trunk/extensions/SecurePoll/includes/Election.php |
— | — | @@ -56,7 +56,10 @@ |
57 | 57 | var $title, $ballotType, $tallyType, $primaryLang, $startDate, $endDate, $authType; |
58 | 58 | |
59 | 59 | /** |
60 | | - * Constructor. |
| 60 | + * Constructor. |
| 61 | + * |
| 62 | + * Do not use this constructor directly, instead use SecurePoll::getElection(). |
| 63 | + * |
61 | 64 | * @param $id integer |
62 | 65 | */ |
63 | 66 | function __construct( $id ) { |
— | — | @@ -293,16 +296,74 @@ |
294 | 297 | } |
295 | 298 | |
296 | 299 | /** |
297 | | - * Get the tallier object |
| 300 | + * Get the tallier objects |
298 | 301 | * @return SecurePoll_Tallier |
299 | 302 | */ |
300 | | - function getTallier() { |
301 | | - $tallier = SecurePoll_Tallier::factory( $this->tallyType, $this ); |
302 | | - if ( !$tallier ) { |
303 | | - throw new MWException( 'Invalid tally type' ); |
| 303 | + function getTalliers() { |
| 304 | + $talliers = array(); |
| 305 | + foreach ( $this->getQuestions() as $question ) { |
| 306 | + $tallier = SecurePoll_Tallier::factory( $this->tallyType, $question ); |
| 307 | + if ( !$tallier ) { |
| 308 | + throw new MWException( 'Invalid tally type' ); |
| 309 | + } |
| 310 | + $talliers[$question->getId()] = $tallier; |
304 | 311 | } |
305 | | - return $tallier; |
| 312 | + return $talliers; |
306 | 313 | } |
307 | 314 | |
| 315 | + /** |
| 316 | + * Call a callback function for each valid vote record, in random order. |
| 317 | + */ |
| 318 | + function dumpVotesToCallback( $callback ) { |
| 319 | + if ( !$this->getCrypt() ) { |
| 320 | + return Status::newFatal( 'securepoll-dump-no-crypt' ); |
| 321 | + } |
| 322 | + |
| 323 | + $random = SecurePoll::getRandom(); |
| 324 | + $status = $random->open(); |
| 325 | + if ( !$status->isOK() ) { |
| 326 | + return $status; |
| 327 | + } |
| 328 | + $db = wfGetDB( DB_SLAVE ); |
| 329 | + $res = $db->select( |
| 330 | + 'securepoll_votes', |
| 331 | + array( '*' ), |
| 332 | + array( |
| 333 | + 'vote_election' => $this->getId(), |
| 334 | + 'vote_current' => 1, |
| 335 | + 'vote_struck' => 0 |
| 336 | + ), |
| 337 | + __METHOD__ |
| 338 | + ); |
| 339 | + if ( $res->numRows() ) { |
| 340 | + $order = $random->shuffle( range( 0, $res->numRows() - 1 ) ); |
| 341 | + foreach ( $order as $i ) { |
| 342 | + $res->seek( $i ); |
| 343 | + call_user_func( $callback, $this, $res->fetchObject() ); |
| 344 | + } |
| 345 | + } |
| 346 | + $random->close(); |
| 347 | + return Status::newGood(); |
| 348 | + } |
| 349 | + |
| 350 | + /** |
| 351 | + * Get an XML snippet describing the configuration of this object |
| 352 | + */ |
| 353 | + function getConfXml() { |
| 354 | + $s = "<configuration>\n" . |
| 355 | + Xml::element( 'title', array(), $this->title ) . "\n" . |
| 356 | + Xml::element( 'ballot', array(), $this->ballotType ) . "\n" . |
| 357 | + Xml::element( 'tally', array(), $this->tallyType ) . "\n" . |
| 358 | + Xml::element( 'lang', array(), $this->primaryLang ) . "\n" . |
| 359 | + Xml::element( 'startDate', array(), wfTimestamp( TS_ISO_8601, $this->startDate ) ) . "\n" . |
| 360 | + Xml::element( 'endDate', array(), wfTimestamp( TS_ISO_8601, $this->endDate ) ) . "\n" . |
| 361 | + Xml::element( 'auth', array(), $this->authType ) . "\n" . |
| 362 | + $this->getConfXmlEntityStuff(); |
| 363 | + foreach ( $this->getQuestions() as $question ) { |
| 364 | + $s .= $question->getConfXml(); |
| 365 | + } |
| 366 | + $s .= "</configuration>\n"; |
| 367 | + return $s; |
| 368 | + } |
308 | 369 | } |
309 | 370 | |
Index: trunk/extensions/SecurePoll/includes/LoginPage.php |
— | — | @@ -14,7 +14,7 @@ |
15 | 15 | } |
16 | 16 | |
17 | 17 | $electionId = intval( $params[0] ); |
18 | | - $this->election = $this->parent->getElection( $electionId ); |
| 18 | + $this->election = SecurePoll::getElection( $electionId ); |
19 | 19 | if ( !$this->election ) { |
20 | 20 | $wgOut->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
21 | 21 | return; |
Index: trunk/extensions/SecurePoll/includes/MessageDumpPage.php |
— | — | @@ -10,7 +10,7 @@ |
11 | 11 | } |
12 | 12 | |
13 | 13 | $electionId = intval( $params[0] ); |
14 | | - $this->election = $this->parent->getElection( $electionId ); |
| 14 | + $this->election = SecurePoll::getElection( $electionId ); |
15 | 15 | if ( !$this->election ) { |
16 | 16 | $wgOut->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
17 | 17 | return; |
— | — | @@ -18,7 +18,7 @@ |
19 | 19 | |
20 | 20 | $wgOut->disable(); |
21 | 21 | header( 'Content-Type: application/x-sql; charset=utf-8' ); |
22 | | - $filename = urlencode( "sp-msgs-$electionId-" . wfTimestampNow() ); |
| 22 | + $filename = urlencode( "sp-msgs-$electionId-" . wfTimestampNow() . '.sql' ); |
23 | 23 | header( "Content-Disposition: attachment; filename=$filename" ); |
24 | 24 | $dbr = wfGetDB( DB_SLAVE ); |
25 | 25 | |
Index: trunk/extensions/SecurePoll/includes/Question.php |
— | — | @@ -31,5 +31,16 @@ |
32 | 32 | return $this->options; |
33 | 33 | } |
34 | 34 | |
35 | | - function getOptions() { return $this->options; } |
| 35 | + function getOptions() { |
| 36 | + return $this->options; |
| 37 | + } |
| 38 | + |
| 39 | + function getConfXml() { |
| 40 | + $s = "<question>\n" . $this->getConfXmlEntityStuff(); |
| 41 | + foreach ( $this->getOptions() as $option ) { |
| 42 | + $s .= $option->getConfXml(); |
| 43 | + } |
| 44 | + $s .= "</question>\n"; |
| 45 | + return $s; |
| 46 | + } |
36 | 47 | } |
Index: trunk/extensions/SecurePoll/includes/ListPage.php |
— | — | @@ -20,7 +20,7 @@ |
21 | 21 | } |
22 | 22 | |
23 | 23 | $electionId = intval( $params[0] ); |
24 | | - $this->election = $this->parent->getElection( $electionId ); |
| 24 | + $this->election = SecurePoll::getElection( $electionId ); |
25 | 25 | if ( !$this->election ) { |
26 | 26 | $wgOut->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
27 | 27 | return; |
— | — | @@ -220,7 +220,7 @@ |
221 | 221 | function formatValue( $name, $value ) { |
222 | 222 | global $wgLang; |
223 | 223 | $critical = Xml::element( 'img', |
224 | | - array( 'src' => $GLOBALS['wgStylePath'] . '/common/images/critical-32.png' ) |
| 224 | + array( 'src' => $GLOBALS['wgScriptPath'] . '/extensions/SecurePoll/resources/critical-32.png' ) |
225 | 225 | ); |
226 | 226 | |
227 | 227 | switch ( $name ) { |
Index: trunk/extensions/SecurePoll/includes/Base.php |
— | — | @@ -0,0 +1,104 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class SecurePoll { |
| 5 | + static $random; |
| 6 | + |
| 7 | + static function getElection( $id ) { |
| 8 | + $db = wfGetDB( DB_MASTER ); |
| 9 | + $row = $db->selectRow( 'securepoll_elections', '*', array( 'el_entity' => $id ), __METHOD__ ); |
| 10 | + if ( $row ) { |
| 11 | + return SecurePoll_Election::newFromRow( $row ); |
| 12 | + } else { |
| 13 | + return false; |
| 14 | + } |
| 15 | + } |
| 16 | + |
| 17 | + static function getElectionByTitle( $name ) { |
| 18 | + $db = wfGetDB( DB_MASTER ); |
| 19 | + $row = $db->selectRow( 'securepoll_elections', '*', array( 'el_title' => $name ), __METHOD__ ); |
| 20 | + if ( $row ) { |
| 21 | + return SecurePoll_Election::newFromRow( $row ); |
| 22 | + } else { |
| 23 | + return false; |
| 24 | + } |
| 25 | + } |
| 26 | + |
| 27 | + static function getRandom() { |
| 28 | + if ( !self::$random ) { |
| 29 | + self::$random = new SecurePoll_Random; |
| 30 | + } |
| 31 | + return self::$random; |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +class SecurePoll_BasePage extends UnlistedSpecialPage { |
| 36 | + static $pages = array( |
| 37 | + 'details' => 'SecurePoll_DetailsPage', |
| 38 | + 'dump' => 'SecurePoll_DumpPage', |
| 39 | + 'entry' => 'SecurePoll_EntryPage', |
| 40 | + 'list' => 'SecurePoll_ListPage', |
| 41 | + 'login' => 'SecurePoll_LoginPage', |
| 42 | + 'msgdump' => 'SecurePoll_MessageDumpPage', |
| 43 | + 'tally' => 'SecurePoll_TallyPage', |
| 44 | + 'translate' => 'SecurePoll_TranslatePage', |
| 45 | + 'vote' => 'SecurePoll_VotePage', |
| 46 | + ); |
| 47 | + |
| 48 | + /** |
| 49 | + * Constructor |
| 50 | + */ |
| 51 | + public function __construct() { |
| 52 | + parent::__construct( 'SecurePoll' ); |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * Show the special page |
| 57 | + * |
| 58 | + * @param $paramString Mixed: parameter passed to the page or null |
| 59 | + */ |
| 60 | + public function execute( $paramString ) { |
| 61 | + global $wgOut, $wgRequest, $wgScriptPath; |
| 62 | + |
| 63 | + wfLoadExtensionMessages( 'SecurePoll' ); |
| 64 | + |
| 65 | + $this->setHeaders(); |
| 66 | + $wgOut->addLink( array( |
| 67 | + 'rel' => 'stylesheet', |
| 68 | + 'href' => "$wgScriptPath/extensions/SecurePoll/resources/SecurePoll.css", |
| 69 | + 'type' => 'text/css' |
| 70 | + ) ); |
| 71 | + $wgOut->addScriptFile( "$wgScriptPath/extensions/SecurePoll/resources/SecurePoll.js" ); |
| 72 | + |
| 73 | + $this->request = $wgRequest; |
| 74 | + |
| 75 | + $paramString = strval( $paramString ); |
| 76 | + if ( $paramString === '' ) { |
| 77 | + $paramString = 'entry'; |
| 78 | + } |
| 79 | + $params = explode( '/', $paramString ); |
| 80 | + $pageName = array_shift( $params ); |
| 81 | + $page = $this->getSubpage( $pageName ); |
| 82 | + if ( !$page ) { |
| 83 | + $wgOut->addWikiMsg( 'securepoll-invalid-page', $pageName ); |
| 84 | + return; |
| 85 | + } |
| 86 | + |
| 87 | + $page->execute( $params ); |
| 88 | + } |
| 89 | + |
| 90 | + function getSubpage( $name ) { |
| 91 | + if ( !isset( self::$pages[$name] ) ) { |
| 92 | + return false; |
| 93 | + } |
| 94 | + $className = self::$pages[$name]; |
| 95 | + $page = new $className( $this ); |
| 96 | + return $page; |
| 97 | + } |
| 98 | + |
| 99 | + function getEditToken() { |
| 100 | + if ( !isset( $_SESSION['spToken'] ) ) { |
| 101 | + $_SESSION['spToken'] = sha1( mt_rand() . mt_rand() . mt_rand() ); |
| 102 | + } |
| 103 | + return $_SESSION['spToken']; |
| 104 | + } |
| 105 | +} |
Property changes on: trunk/extensions/SecurePoll/includes/Base.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
Added: svn:eol-style |
1 | 106 | + native |
Index: trunk/extensions/SecurePoll/includes/VotePage.php |
— | — | @@ -19,7 +19,7 @@ |
20 | 20 | } |
21 | 21 | |
22 | 22 | $electionId = intval( $params[0] ); |
23 | | - $this->election = $this->parent->getElection( $electionId ); |
| 23 | + $this->election = SecurePoll::getElection( $electionId ); |
24 | 24 | if ( !$this->election ) { |
25 | 25 | $wgOut->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
26 | 26 | return; |
— | — | @@ -236,6 +236,7 @@ |
237 | 237 | throw new MWException( 'Configuration error: no jump-id' ); |
238 | 238 | } |
239 | 239 | $url .= "/login/$id"; |
| 240 | + wfRunHooks( 'SecurePoll_JumpUrl', array( $this, &$url ) ); |
240 | 241 | $wgOut->addWikiText( $this->election->getMessage( 'jump-text' ) ); |
241 | 242 | $wgOut->addHTML( |
242 | 243 | Xml::openElement( 'form', array( 'action' => $url, 'method' => 'post' ) ) . |
Index: trunk/extensions/SecurePoll/includes/Entity.php |
— | — | @@ -15,8 +15,9 @@ |
16 | 16 | var $messagesLoaded = array(); |
17 | 17 | var $properties; |
18 | 18 | |
19 | | - static $languages = array(); |
| 19 | + static $languages = array( 'en' ); |
20 | 20 | static $messageCache = array(); |
| 21 | + static $parserOptions; |
21 | 22 | |
22 | 23 | /** |
23 | 24 | * Create an entity of the given type. This is typically called from the |
— | — | @@ -182,6 +183,31 @@ |
183 | 184 | } |
184 | 185 | |
185 | 186 | /** |
| 187 | + * Get a message, and interpret it as wikitext, converting it to HTML. |
| 188 | + */ |
| 189 | + function parseMessage( $name, $lineStart = true ) { |
| 190 | + global $wgParser, $wgTitle; |
| 191 | + if ( !self::$parserOptions ) { |
| 192 | + self::$parserOptions = new ParserOptions; |
| 193 | + } |
| 194 | + if ( $wgTitle ) { |
| 195 | + $title = $wgTitle; |
| 196 | + } else { |
| 197 | + $title = SpecialPage::getTitleFor( 'SecurePoll' ); |
| 198 | + } |
| 199 | + $wikiText = $this->getMessage( $name ); |
| 200 | + $out = $wgParser->parse( $wikiText, $title, self::$parserOptions, $lineStart ); |
| 201 | + return $out->getText(); |
| 202 | + } |
| 203 | + |
| 204 | + /** |
| 205 | + * Get a message and convert it from wikitext to HTML, without <p> tags. |
| 206 | + */ |
| 207 | + function parseMessageInline( $name ) { |
| 208 | + return $this->parseMessage( $name, false ); |
| 209 | + } |
| 210 | + |
| 211 | + /** |
186 | 212 | * Get a property value. If it does not exist, the $default parameter |
187 | 213 | * is passed back. |
188 | 214 | * @param $name string |
— | — | @@ -198,4 +224,40 @@ |
199 | 225 | } |
200 | 226 | } |
201 | 227 | |
| 228 | + /** |
| 229 | + * Get all defined properties as an associative array |
| 230 | + */ |
| 231 | + function getAllProperties() { |
| 232 | + if ( $this->properties === null ) { |
| 233 | + $this->loadProperties(); |
| 234 | + } |
| 235 | + return $this->properties; |
| 236 | + } |
| 237 | + |
| 238 | + /** |
| 239 | + * Get configuration XML. Overridden by most subclasses. |
| 240 | + */ |
| 241 | + function getConfXml() { |
| 242 | + return "<{$this->type}>\n" . |
| 243 | + $this->getConfXmlEntityStuff() . |
| 244 | + "</{$this->type}>\n"; |
| 245 | + } |
| 246 | + |
| 247 | + /** |
| 248 | + * Get an XML snippet giving the messages and properties |
| 249 | + */ |
| 250 | + function getConfXmlEntityStuff() { |
| 251 | + $s = Xml::element( 'id', array(), $this->getId() ) . "\n"; |
| 252 | + foreach ( $this->getAllProperties() as $name => $value ) { |
| 253 | + $s .= Xml::element( 'property', array( 'name' => $name ), $value ) . "\n"; |
| 254 | + } |
| 255 | + foreach ( $this->getMessageNames() as $name ) { |
| 256 | + foreach ( self::$languages as $lang ) { |
| 257 | + $s .= Xml::element( 'message', array( 'name' => $name, 'lang' => $lang ), |
| 258 | + $this->getRawMessage( $name, $lang ) ) . "\n"; |
| 259 | + } |
| 260 | + } |
| 261 | + return $s; |
| 262 | + } |
| 263 | + |
202 | 264 | } |
Index: trunk/extensions/SecurePoll/includes/Ballot.php |
— | — | @@ -14,11 +14,11 @@ |
15 | 15 | abstract function getTallyTypes(); |
16 | 16 | |
17 | 17 | /** |
18 | | - * Get the HTML for this ballot. <form> tags should not be included, |
19 | | - * they will be added by the VotePage. |
| 18 | + * Get the HTML form segment for a single question |
| 19 | + * @param $question SecurePoll_Question |
20 | 20 | * @return string |
21 | 21 | */ |
22 | | - abstract function getForm(); |
| 22 | + abstract function getQuestionForm( $question ); |
23 | 23 | |
24 | 24 | /** |
25 | 25 | * Called when the form is submitted. This returns a Status object which, |
— | — | @@ -58,6 +58,28 @@ |
59 | 59 | function __construct( $election ) { |
60 | 60 | $this->election = $election; |
61 | 61 | } |
| 62 | + |
| 63 | + /** |
| 64 | + * Get the HTML for this ballot. <form> tags should not be included, |
| 65 | + * they will be added by the VotePage. |
| 66 | + * @return string |
| 67 | + */ |
| 68 | + function getForm() { |
| 69 | + global $wgParser, $wgTitle; |
| 70 | + $questions = $this->election->getQuestions(); |
| 71 | + if ( $this->election->getProperty( 'shuffle-questions' ) ) { |
| 72 | + shuffle( $questions ); |
| 73 | + } |
| 74 | + |
| 75 | + $s = ''; |
| 76 | + foreach ( $questions as $question ) { |
| 77 | + $s .= "<hr/>\n" . |
| 78 | + $question->parseMessage( 'text' ) . |
| 79 | + $this->getQuestionForm( $question ) . |
| 80 | + "\n"; |
| 81 | + } |
| 82 | + return $s; |
| 83 | + } |
62 | 84 | } |
63 | 85 | |
64 | 86 | /** |
— | — | @@ -79,37 +101,25 @@ |
80 | 102 | } |
81 | 103 | |
82 | 104 | /** |
83 | | - * Get the HTML for this ballot. |
| 105 | + * Get the HTML form segment for a single question |
| 106 | + * @param $question SecurePoll_Question |
84 | 107 | * @return string |
85 | 108 | */ |
86 | | - function getForm() { |
87 | | - global $wgParser, $wgTitle; |
88 | | - $questions = $this->election->getQuestions(); |
89 | | - if ( $this->election->getProperty( 'shuffle-questions' ) ) { |
90 | | - shuffle( $questions ); |
| 109 | + function getQuestionForm( $question ) { |
| 110 | + $options = $question->getChildren(); |
| 111 | + if ( $this->election->getProperty( 'shuffle-options' ) ) { |
| 112 | + shuffle( $options ); |
91 | 113 | } |
92 | | - |
| 114 | + $name = 'securepoll_q' . $question->getId(); |
93 | 115 | $s = ''; |
94 | | - $parserOpts = new ParserOptions; |
95 | | - |
96 | | - foreach ( $questions as $question ) { |
97 | | - $s .= "<hr/>\n"; |
98 | | - $s .= $wgParser->parse( $question->getMessage( 'text' ), $wgTitle, $parserOpts )->getText(); |
99 | | - $options = $question->getChildren(); |
100 | | - if ( $this->election->getProperty( 'shuffle-options' ) ) { |
101 | | - shuffle( $options ); |
102 | | - } |
103 | | - $name = 'securepoll_q' . $question->getId(); |
104 | | - foreach ( $options as $option ) { |
105 | | - $optionText = $option->getMessage( 'text' ); |
106 | | - $optionHTML = $wgParser->parse( $optionText, $wgTitle, $parserOpts, false )->getText(); |
107 | | - $optionId = $option->getId(); |
108 | | - $radioId = "{$name}_opt{$optionId}"; |
109 | | - $s .= Xml::radio( $name, $optionId, false, array( 'id' => $radioId ) ) . |
110 | | - ' ' . |
111 | | - Xml::tags( 'label', array( 'for' => $radioId ), $optionText ) . |
112 | | - "<br/>\n"; |
113 | | - } |
| 116 | + foreach ( $options as $option ) { |
| 117 | + $optionHTML = $option->parseMessageInline( 'text' ); |
| 118 | + $optionId = $option->getId(); |
| 119 | + $radioId = "{$name}_opt{$optionId}"; |
| 120 | + $s .= Xml::radio( $name, $optionId, false, array( 'id' => $radioId ) ) . |
| 121 | + ' ' . |
| 122 | + Xml::tags( 'label', array( 'for' => $radioId ), $optionHTML ) . |
| 123 | + "<br/>\n"; |
114 | 124 | } |
115 | 125 | return $s; |
116 | 126 | } |
— | — | @@ -154,61 +164,99 @@ |
155 | 165 | } |
156 | 166 | |
157 | 167 | /** |
158 | | - * TODO: this is code copied directly from BoardVote, it needs to be ported. |
| 168 | + * Ballot for preferential voting |
| 169 | + * Properties: |
| 170 | + * shuffle-questions |
| 171 | + * shuffle-options |
| 172 | + * must-rank-all |
159 | 173 | */ |
160 | 174 | class SecurePoll_PreferentialBallot extends SecurePoll_Ballot { |
161 | 175 | function getTallyTypes() { |
162 | | - return array( 'plurality', 'condorcet' ); |
| 176 | + return array( 'schulze' ); |
163 | 177 | } |
164 | 178 | |
165 | | - function getRecord() { |
166 | | - global $wgBoardCandidates; |
167 | | - |
168 | | - $record = "I prefer: "; |
169 | | - $num_candidates = count( $wgBoardCandidates ); |
170 | | - $cnt = 0; |
171 | | - foreach ( $this->mVotedFor as $i => $rank ) { |
172 | | - $cnt++; |
173 | | - |
174 | | - $record .= $wgBoardCandidates[ $i ] . "["; |
175 | | - $record .= ( $rank == '' ) ? 100 : $rank; |
176 | | - $record .= "]"; |
177 | | - $record .= ( $cnt != $num_candidates ) ? ", " : ""; |
| 179 | + function getQuestionForm( $question ) { |
| 180 | + global $wgRequest; |
| 181 | + $options = $question->getChildren(); |
| 182 | + if ( $this->election->getProperty( 'shuffle-options' ) ) { |
| 183 | + shuffle( $options ); |
178 | 184 | } |
179 | | - $record .= "\n"; |
| 185 | + $name = 'securepoll_q' . $question->getId(); |
| 186 | + $s = ''; |
| 187 | + foreach ( $options as $option ) { |
| 188 | + $optionHTML = $option->parseMessageInline( 'text' ); |
| 189 | + $optionId = $option->getId(); |
| 190 | + $inputId = "{$name}_opt{$optionId}"; |
| 191 | + $oldValue = $wgRequest->getVal( $inputId, '' ); |
| 192 | + $s .= |
| 193 | + Xml::input( $inputId, '3', $oldValue, array( |
| 194 | + 'id' => $inputId, |
| 195 | + 'maxlength' => 3, |
| 196 | + ) ) . |
| 197 | + ' ' . |
| 198 | + Xml::tags( 'label', array( 'for' => $inputId ), $optionHTML ) . |
| 199 | + ' ' . |
| 200 | + "<br/>\n"; |
| 201 | + } |
| 202 | + return $s; |
| 203 | + } |
180 | 204 | |
181 | | - // Pad it out with spaces to a constant length, so that the encrypted record is secure |
182 | | - $padLength = array_sum( array_map( 'strlen', $wgBoardCandidates ) ) + $num_candidates * 8 + 20; |
183 | | - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ |
184 | | - // length of the candidate names added together room for rank & separators extra |
| 205 | + function submitForm() { |
| 206 | + global $wgRequest; |
| 207 | + $questions = $this->election->getQuestions(); |
| 208 | + $record = ''; |
| 209 | + $status = Status::newGood(); |
185 | 210 | |
186 | | - $record = str_pad( $record, $padLength ); |
187 | | - return $record; |
188 | | - } |
| 211 | + foreach ( $questions as $question ) { |
| 212 | + $options = $question->getOptions(); |
| 213 | + foreach ( $options as $option ) { |
| 214 | + $id = 'securepoll_q' . $question->getId() . '_opt' . $option->getId(); |
| 215 | + $rank = $wgRequest->getVal( $id ); |
189 | 216 | |
190 | | - function validVote() { |
191 | | - foreach ( $this->mVotedFor as $rank ) { |
192 | | - if ( $rank != '' ) { |
193 | | - if ( !preg_match( '/^[1-9]\d?$/', $rank ) ) { |
194 | | - return false; |
| 217 | + if ( is_numeric( $rank ) ) { |
| 218 | + if ( $rank <= 0 || $rank >= 1000 ) { |
| 219 | + $status->fatal( 'securepoll-invalid-rank', $id ); |
| 220 | + continue; |
| 221 | + } else { |
| 222 | + $rank = intval( $rank ); |
| 223 | + } |
| 224 | + } elseif ( strval( $rank ) === '' ) { |
| 225 | + if ( $this->election->getProperty( 'must-rank-all' ) ) { |
| 226 | + $status->fatal( 'securepoll-unranked-options', $id ); |
| 227 | + continue; |
| 228 | + } else { |
| 229 | + $rank = 1000; |
| 230 | + } |
| 231 | + } else { |
| 232 | + $status->fatal( 'securepoll-invalid-rank', $id ); |
| 233 | + continue; |
195 | 234 | } |
| 235 | + $record .= sprintf( 'Q%08X-A%08X-R%08X--', |
| 236 | + $question->getId(), $option->getId(), $rank ); |
196 | 237 | } |
197 | 238 | } |
198 | | - |
199 | | - return true; |
| 239 | + if ( $status->isOK() ) { |
| 240 | + $status->value = $record . "\n"; |
| 241 | + } |
| 242 | + return $status; |
200 | 243 | } |
201 | 244 | |
202 | | - function voteEntry( $index, $candidate ) { |
203 | | - return " |
204 | | - <tr><td align=\"right\"> |
205 | | - <input type=\"text\" maxlength=\"2\" size=\"2\" name=\"candidate[{$index}]\" /> |
206 | | - </td><td align=\"left\"> |
207 | | - $candidate |
208 | | - </td></tr>"; |
| 245 | + function unpackRecord( $record ) { |
| 246 | + $ranks = array(); |
| 247 | + $itemLength = 3*8 + 7; |
| 248 | + for ( $offset = 0; $offset < strlen( $record ); $offset += $itemLength ) { |
| 249 | + if ( !preg_match( '/Q([0-9A-F]{8})-A([0-9A-F]{8})-R([0-9A-F]{8})--/A', |
| 250 | + $record, $m, 0, $offset ) ) |
| 251 | + { |
| 252 | + wfDebug( __METHOD__.": regex doesn't match\n" ); |
| 253 | + return false; |
| 254 | + } |
| 255 | + $qid = intval( base_convert( $m[1], 16, 10 ) ); |
| 256 | + $oid = intval( base_convert( $m[2], 16, 10 ) ); |
| 257 | + $rank = intval( base_convert( $m[3], 16, 10 ) ); |
| 258 | + $ranks[$qid][$oid] = $rank; |
| 259 | + } |
| 260 | + return $ranks; |
209 | 261 | } |
210 | | - |
211 | | - function getForm() { } |
212 | | - function submitForm() { } |
213 | | - function unpackRecord( $record ) {} |
214 | 262 | } |
215 | 263 | |
Index: trunk/extensions/SecurePoll/includes/Random.php |
— | — | @@ -0,0 +1,70 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class SecurePoll_Random { |
| 5 | + var $urandom; |
| 6 | + |
| 7 | + /** |
| 8 | + * Open a /dev/urandom file handle |
| 9 | + * Returns a Status object |
| 10 | + */ |
| 11 | + function open() { |
| 12 | + if ( $this->urandom ) { |
| 13 | + return Status::newGood(); |
| 14 | + } |
| 15 | + |
| 16 | + if ( wfIsWindows() ) { |
| 17 | + return Status::newFatal( 'securepoll-urandom-not-supported' ); |
| 18 | + } |
| 19 | + $this->urandom = fopen( '/dev/urandom', 'rb' ); |
| 20 | + if ( !$this->urandom ) { |
| 21 | + return Status::newFatal( 'securepoll-dump-no-urandom' ); |
| 22 | + } |
| 23 | + return Status::newGood(); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Close any open file handles |
| 28 | + */ |
| 29 | + function close() { |
| 30 | + if ( $this->urandom ) { |
| 31 | + fclose( $this->urandom ); |
| 32 | + $this->urandom = null; |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + /** |
| 37 | + * Get a random integer between 0 and ($maxp1 - 1). |
| 38 | + * Should only be called after open() succeeds. |
| 39 | + */ |
| 40 | + function getInt( $maxp1 ) { |
| 41 | + $numBytes = ceil( strlen( base_convert( $maxp1, 10, 16 ) ) / 2 ); |
| 42 | + if ( $numBytes == 0 ) { |
| 43 | + return 0; |
| 44 | + } |
| 45 | + $data = fread( $this->urandom, $numBytes ); |
| 46 | + if ( strlen( $data ) != $numBytes ) { |
| 47 | + throw new MWException( __METHOD__.': not enough bytes' ); |
| 48 | + } |
| 49 | + $x = 0; |
| 50 | + for ( $i = 0; $i < $numBytes; $i++ ) { |
| 51 | + $x *= 256; |
| 52 | + $x += ord( substr( $data, $i, 1 ) ); |
| 53 | + } |
| 54 | + return $x % $maxp1; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Works like shuffle() except more secure. Returns the new array instead |
| 59 | + * of modifying it. The algorithm is the Knuth/Durstenfeld kind. |
| 60 | + */ |
| 61 | + function shuffle( $a ) { |
| 62 | + $a = array_values( $a ); |
| 63 | + for ( $i = count( $a ) - 1; $i >= 0; $i-- ) { |
| 64 | + $target = $this->getInt( $i + 1 ); |
| 65 | + $tmp = $a[$i]; |
| 66 | + $a[$i] = $a[$target]; |
| 67 | + $a[$target] = $tmp; |
| 68 | + } |
| 69 | + return $a; |
| 70 | + } |
| 71 | +} |
Property changes on: trunk/extensions/SecurePoll/includes/Random.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 72 | + native |
Index: trunk/extensions/SecurePoll/includes/TallyPage.php |
— | — | @@ -17,7 +17,7 @@ |
18 | 18 | } |
19 | 19 | |
20 | 20 | $electionId = intval( $params[0] ); |
21 | | - $this->election = $this->parent->getElection( $electionId ); |
| 21 | + $this->election = SecurePoll::getElection( $electionId ); |
22 | 22 | if ( !$this->election ) { |
23 | 23 | $wgOut->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
24 | 24 | return; |
— | — | @@ -136,19 +136,46 @@ |
137 | 137 | ), __METHOD__ |
138 | 138 | ); |
139 | 139 | $crypt = $this->election->getCrypt(); |
140 | | - $tallier = $this->election->getTallier(); |
| 140 | + $ballot = $this->election->getBallot(); |
| 141 | + $questions = $this->election->getQuestions(); |
| 142 | + $talliers = $this->election->getTalliers(); |
141 | 143 | foreach ( $res as $row ) { |
142 | | - $status = $crypt->decrypt( $row->vote_record ); |
143 | | - if ( !$status->isOK() ) { |
144 | | - $wgOut->addWikiText( $status->getWikiText() ); |
145 | | - return; |
| 144 | + $record = $row->vote_record; |
| 145 | + if ( $crypt ) { |
| 146 | + $status = $crypt->decrypt( $record ); |
| 147 | + if ( !$status->isOK() ) { |
| 148 | + $wgOut->addWikiText( $status->getWikiText() ); |
| 149 | + return; |
| 150 | + } |
| 151 | + $record = $status->value; |
146 | 152 | } |
147 | | - if ( !$tallier->addRecord( $status->value ) ) { |
148 | | - $wgOut->addWikiMsg( 'securepoll-tally-error' ); |
149 | | - return; |
| 153 | + $record = rtrim( $record ); |
| 154 | + $scores = $ballot->unpackRecord( $record ); |
| 155 | + foreach ( $questions as $question ) { |
| 156 | + $qid = $question->getId(); |
| 157 | + if ( !isset( $scores[$qid] ) ) { |
| 158 | + $wgOut->addWikiMsg( 'securepoll-tally-error' ); |
| 159 | + return; |
| 160 | + } |
| 161 | + if ( !$talliers[$qid]->addVote( $scores[$qid] ) ) { |
| 162 | + $wgOut->addWikiMsg( 'securepoll-tally-error' ); |
| 163 | + return; |
| 164 | + } |
150 | 165 | } |
151 | 166 | } |
152 | | - $wgOut->addHTML( $tallier->getResult() ); |
| 167 | + $first = true; |
| 168 | + foreach ( $questions as $question ) { |
| 169 | + if ( $first ) { |
| 170 | + $first = false; |
| 171 | + } else { |
| 172 | + $wgOut->addHTML( "<hr/>\n" ); |
| 173 | + } |
| 174 | + $tallier = $talliers[$question->getId()]; |
| 175 | + $tallier->finishTally(); |
| 176 | + $wgOut->addHTML( |
| 177 | + $question->parseMessage( 'text' ) . |
| 178 | + $tallier->getHtmlResult() ); |
| 179 | + } |
153 | 180 | } |
154 | 181 | |
155 | 182 | /** |
— | — | @@ -182,7 +209,8 @@ |
183 | 210 | return; |
184 | 211 | } |
185 | 212 | } |
186 | | - $wgOut->addHTML( $tallier->getResult() ); |
| 213 | + $tallier->finishTally(); |
| 214 | + $wgOut->addHTML( $tallier->getHtmlResult() ); |
187 | 215 | } |
188 | 216 | |
189 | 217 | function getTitle() { |
Index: trunk/extensions/SecurePoll/includes/TranslatePage.php |
— | — | @@ -17,7 +17,7 @@ |
18 | 18 | } |
19 | 19 | |
20 | 20 | $electionId = intval( $params[0] ); |
21 | | - $this->election = $this->parent->getElection( $electionId ); |
| 21 | + $this->election = SecurePoll::getElection( $electionId ); |
22 | 22 | if ( !$this->election ) { |
23 | 23 | $wgOut->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
24 | 24 | return; |
Index: trunk/extensions/SecurePoll/includes/Tallier.php |
— | — | @@ -1,22 +1,27 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | abstract class SecurePoll_Tallier { |
5 | | - var $election; |
| 5 | + var $question; |
6 | 6 | |
7 | | - abstract function addRecord( $record ); |
8 | | - abstract function getResult(); |
| 7 | + abstract function addVote( $scores ); |
| 8 | + abstract function getHtmlResult(); |
| 9 | + abstract function getTextResult(); |
9 | 10 | |
10 | | - static function factory( $type, $election ) { |
| 11 | + abstract function finishTally(); |
| 12 | + |
| 13 | + static function factory( $type, $question ) { |
11 | 14 | switch ( $type ) { |
12 | 15 | case 'plurality': |
13 | | - return new SecurePoll_PluralityTallier( $election ); |
| 16 | + return new SecurePoll_PluralityTallier( $question ); |
| 17 | + case 'schulze': |
| 18 | + return new SecurePoll_SchulzeTallier( $question ); |
14 | 19 | default: |
15 | 20 | throw new MWException( "Invalid tallier type: $type" ); |
16 | 21 | } |
17 | 22 | } |
18 | 23 | |
19 | | - function __construct( $election ) { |
20 | | - $this->election = $election; |
| 24 | + function __construct( $question ) { |
| 25 | + $this->question = $question; |
21 | 26 | } |
22 | 27 | } |
23 | 28 | |
— | — | @@ -26,63 +31,204 @@ |
27 | 32 | class SecurePoll_PluralityTallier extends SecurePoll_Tallier { |
28 | 33 | var $tally = array(); |
29 | 34 | |
30 | | - function __construct( $election ) { |
31 | | - parent::__construct( $election ); |
32 | | - $questions = $this->election->getQuestions(); |
33 | | - foreach ( $questions as $question ) { |
34 | | - foreach ( $question->getOptions() as $option ) { |
35 | | - $this->tally[$question->getId()][$option->getId()] = 0; |
| 35 | + function __construct( $question ) { |
| 36 | + parent::__construct( $question ); |
| 37 | + foreach ( $question->getOptions() as $option ) { |
| 38 | + $this->tally[$option->getId()] = 0; |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + function addVote( $scores ) { |
| 43 | + foreach ( $scores as $oid => $score ) { |
| 44 | + if ( !isset( $this->tally[$oid] ) ) { |
| 45 | + wfDebug( __METHOD__.": unknown OID $oid\n" ); |
| 46 | + return false; |
36 | 47 | } |
| 48 | + $this->tally[$oid] += $score; |
37 | 49 | } |
| 50 | + return true; |
38 | 51 | } |
39 | 52 | |
40 | | - function addRecord( $record ) { |
41 | | - $i = 0; |
42 | | - $ballot = $this->election->getBallot(); |
43 | | - $scores = $ballot->unpackRecord( $record ); |
44 | | - if ( $scores === false ) { |
45 | | - return false; |
| 53 | + function finishTally() { |
| 54 | + // Sort the scores |
| 55 | + arsort( $this->tally ); |
| 56 | + } |
| 57 | + |
| 58 | + function getHtmlResult() { |
| 59 | + // Show the results |
| 60 | + $s = ''; |
| 61 | + |
| 62 | + foreach ( $this->question->getOptions() as $option ) { |
| 63 | + $s .= '<tr><td>' . $option->getMessage( 'text' ) . "</td>\n" . |
| 64 | + '<td>' . $this->tally[$option->getId()] . "</td>\n" . |
| 65 | + "</tr>\n"; |
46 | 66 | } |
47 | | - foreach ( $scores as $qid => $questionScores ) { |
48 | | - if ( !isset( $this->tally[$qid] ) ) { |
49 | | - wfDebug( __METHOD__.": unknown QID $qid\n" ); |
| 67 | + $s .= "</table>\n"; |
| 68 | + return $s; |
| 69 | + } |
| 70 | + |
| 71 | + function getTextResult() { |
| 72 | + // Calculate column width |
| 73 | + $width = 10; |
| 74 | + foreach ( $question->getOptions() as $option ) { |
| 75 | + $width = max( $width, strlen( $option->getMessage( 'text' ) ) ); |
| 76 | + } |
| 77 | + if ( $width > 57 ) { |
| 78 | + $width = 57; |
| 79 | + } |
| 80 | + |
| 81 | + // Show the results |
| 82 | + $s = wordwrap( $question->getMessage( 'text' ) ) . "\n"; |
| 83 | + foreach ( $question->getOptions() as $option ) { |
| 84 | + $otext = $option->getMessage( 'text' ); |
| 85 | + if ( strlen( $otext ) > $width ) { |
| 86 | + $otext = substr( $otext, 0, $width - 3 ) . '...'; |
| 87 | + } else { |
| 88 | + $otext = str_pad( $otext, $width ); |
| 89 | + } |
| 90 | + $s .= $otext . ' | ' . |
| 91 | + $this->tally[$question->getId()][$option->getId()] . "\n"; |
| 92 | + } |
| 93 | + return $s; |
| 94 | + } |
| 95 | + |
| 96 | + function getRanks() { |
| 97 | + $ranks = array(); |
| 98 | + $currentRank = 1; |
| 99 | + $oids = array_keys( $this->tally ); |
| 100 | + $scores = array_values( $this->tally ); |
| 101 | + foreach ( $oids as $i => $oid ) { |
| 102 | + if ( $i > 0 && $scores[$i-1] !== $scores[$i] ) { |
| 103 | + $currentRank = $i + 1; |
| 104 | + } |
| 105 | + $ranks[$oid] = $currentRank; |
| 106 | + } |
| 107 | + return $ranks; |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +abstract class SecurePoll_PairwiseTallier extends SecurePoll_Tallier { |
| 112 | + var $optionIds = array(); |
| 113 | + var $victories = array(); |
| 114 | + |
| 115 | + function __construct( $question ) { |
| 116 | + parent::__construct( $question ); |
| 117 | + $this->optionIds = array(); |
| 118 | + foreach ( $question->getOptions() as $option ) { |
| 119 | + $this->optionIds[] = $option->getId(); |
| 120 | + } |
| 121 | + |
| 122 | + $this->victories = array(); |
| 123 | + foreach ( $this->optionIds as $i ) { |
| 124 | + foreach ( $this->optionIds as $j ) { |
| 125 | + $this->victories[$i][$j] = 0; |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + function addVote( $ranks ) { |
| 131 | + foreach ( $this->optionIds as $oid1 ) { |
| 132 | + if ( !isset( $ranks[$oid1] ) ) { |
| 133 | + wfDebug( "Invalid vote record, missing option $oid1\n" ); |
50 | 134 | return false; |
51 | 135 | } |
52 | | - foreach ( $questionScores as $oid => $score ) { |
53 | | - if ( !isset( $this->tally[$qid][$oid] ) ) { |
54 | | - wfDebug( __METHOD__.": unknown OID $oid\n" ); |
55 | | - return false; |
| 136 | + foreach ( $this->optionIds as $oid2 ) { |
| 137 | + # Lower = better |
| 138 | + if ( $ranks[$oid1] < $ranks[$oid2] ) { |
| 139 | + $this->victories[$oid1][$oid2]++; |
56 | 140 | } |
57 | | - $this->tally[$qid][$oid] += $score; |
58 | 141 | } |
59 | 142 | } |
60 | 143 | return true; |
61 | 144 | } |
| 145 | +} |
62 | 146 | |
63 | | - function getResult() { |
64 | | - global $wgOut; |
65 | | - $questions = $this->election->getQuestions(); |
| 147 | +/** |
| 148 | + * This is the basic Schulze method with no tie-breaking. |
| 149 | + */ |
| 150 | +class SecurePoll_SchulzeTallier extends SecurePoll_PairwiseTallier { |
| 151 | + var $strengths; |
| 152 | + |
| 153 | + function finishTally() { |
| 154 | + # This algorithm follows Markus Schulze, "A New Monotonic, Clone-Independent, Reversal |
| 155 | + # Symmetric, and Condorcet-Consistent Single-Winner Election Method" |
66 | 156 | |
67 | | - // Sort the scores |
68 | | - foreach ( $this->tally as &$scores ) { |
69 | | - arsort( $scores ); |
| 157 | + $this->strengths = array(); |
| 158 | + foreach ( $this->optionIds as $oid1 ) { |
| 159 | + foreach ( $this->optionIds as $oid2 ) { |
| 160 | + if ( $oid1 === $oid2 ) { |
| 161 | + continue; |
| 162 | + } |
| 163 | + if ( $this->victories[$oid1][$oid2] > $this->victories[$oid2][$oid1] ) { |
| 164 | + $this->strengths[$oid1][$oid2] = $this->victories[$oid1][$oid2]; |
| 165 | + } else { |
| 166 | + $this->strengths[$oid1][$oid2] = 0; |
| 167 | + } |
| 168 | + } |
70 | 169 | } |
71 | 170 | |
72 | | - // Show the results |
73 | | - $s = ''; |
74 | | - foreach ( $questions as $question ) { |
75 | | - if ( $s !== '' ) { |
76 | | - $s .= "<hr/>\n"; |
| 171 | + foreach ( $this->optionIds as $oid1 ) { |
| 172 | + foreach ( $this->optionIds as $oid2 ) { |
| 173 | + if ( $oid1 === $oid2 ) { |
| 174 | + continue; |
| 175 | + } |
| 176 | + foreach ( $this->optionIds as $oid3 ) { |
| 177 | + if ( $oid1 === $oid3 || $oid2 === $oid3 ) { |
| 178 | + continue; |
| 179 | + } |
| 180 | + $this->strengths[$oid2][$oid3] = max( |
| 181 | + $this->strengths[$oid2][$oid3], |
| 182 | + min( |
| 183 | + $this->strengths[$oid2][$oid1], |
| 184 | + $this->strengths[$oid1][$oid3] |
| 185 | + ) |
| 186 | + ); |
| 187 | + } |
77 | 188 | } |
78 | | - $s .= $wgOut->parse( $question->getMessage( 'text' ) ) . |
79 | | - '<table class="securepoll-result-table" border="1">'; |
80 | | - foreach ( $question->getOptions() as $option ) { |
81 | | - $s .= '<tr><td>' . $option->getMessage( 'text' ) . "</td>\n" . |
82 | | - '<td>' . $this->tally[$question->getId()][$option->getId()] . "</td>\n" . |
83 | | - "</tr>\n"; |
| 189 | + } |
| 190 | + |
| 191 | + # Calculate ranks |
| 192 | + $this->ranks = array(); |
| 193 | + $rankedOptions = $this->optionIds; |
| 194 | + usort( $rankedOptions, array( $this, 'comparePair' ) ); |
| 195 | + $rankedOptions = array_reverse( $rankedOptions ); |
| 196 | + $currentRank = 1; |
| 197 | + foreach ( $rankedOptions as $i => $oid ) { |
| 198 | + if ( $i > 0 && $this->comparePair( $rankedOptions[$i-1], $oid ) ) { |
| 199 | + $currentRank = $i + 1; |
84 | 200 | } |
85 | | - $s .= "</table>\n"; |
| 201 | + $this->ranks[$oid] = $currentRank; |
86 | 202 | } |
87 | | - return $s; |
88 | 203 | } |
| 204 | + |
| 205 | + function comparePair( $i, $j ) { |
| 206 | + if ( $i === $j ) { |
| 207 | + return 0; |
| 208 | + } |
| 209 | + $sij = $this->strengths[$i][$j]; |
| 210 | + $sji = $this->strengths[$j][$i]; |
| 211 | + if ( $sij > $sji ) { |
| 212 | + return 1; |
| 213 | + } elseif ( $sji > $sij ) { |
| 214 | + return -1; |
| 215 | + } else { |
| 216 | + return 0; |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + function getHtmlResult() { |
| 221 | + return '<pre>' . $this->getTextResult() . '</pre>'; |
| 222 | + |
| 223 | + } |
| 224 | + |
| 225 | + function getTextResult() { |
| 226 | + return |
| 227 | + "Victory matrix:\n" . |
| 228 | + var_export( $this->victories, true ) . "\n\n" . |
| 229 | + "Path strength matrix:\n" . |
| 230 | + var_export( $this->strengths, true ) . "\n\n" . |
| 231 | + "Ranks:\n" . |
| 232 | + var_export( $this->ranks, true ) . "\n"; |
| 233 | + } |
89 | 234 | } |
| 235 | + |
Index: trunk/extensions/SecurePoll/cli/dump.php |
— | — | @@ -0,0 +1,65 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Generate an XML dump of an election, including configuration and votes. |
| 6 | + */ |
| 7 | + |
| 8 | + |
| 9 | +$optionsWithArgs = array( 'o' ); |
| 10 | +require( dirname(__FILE__).'/cli.inc' ); |
| 11 | + |
| 12 | +if ( !isset( $args[0] ) ) { |
| 13 | + spFatal( "Usage: php dump.php [-o <outfile>] <election name>" ); |
| 14 | +} |
| 15 | + |
| 16 | +$election = SecurePoll::getElectionByTitle( $args[0] ); |
| 17 | +if ( !$election ) { |
| 18 | + spFatal( "There is no election called \"$args[0]\"" ); |
| 19 | +} |
| 20 | + |
| 21 | +if ( !isset( $options['o'] ) ) { |
| 22 | + $fileName = '-'; |
| 23 | +} else { |
| 24 | + $fileName = $options['o']; |
| 25 | +} |
| 26 | +if ( $fileName === '-' ) { |
| 27 | + $outFile = STDIN; |
| 28 | +} else { |
| 29 | + $outFile = fopen( $fileName, 'w' ); |
| 30 | +} |
| 31 | +if ( !$outFile ) { |
| 32 | + spFatal( "Unable to open $fileName for writing" ); |
| 33 | +} |
| 34 | + |
| 35 | +SecurePoll_Entity::setLanguages( array( $election->getLanguage() ) ); |
| 36 | + |
| 37 | +$cbdata = array( |
| 38 | + 'header' => "<SecurePoll>\n<election>\n" . $election->getConfXml(), |
| 39 | + 'outFile' => $outFile |
| 40 | +); |
| 41 | + |
| 42 | +# Write vote records |
| 43 | +$election->cbdata = $cbdata; |
| 44 | +$status = $election->dumpVotesToCallback( 'spDumpVote' ); |
| 45 | +if ( !$status->isOK() ) { |
| 46 | + spFatal( $status->getWikiText() ); |
| 47 | +} |
| 48 | +if ( $election->cbdata['header'] ) { |
| 49 | + echo $election->cbdata['header']; |
| 50 | +} |
| 51 | + |
| 52 | +fwrite( $outFile, "</election>\n</SecurePoll>\n" ); |
| 53 | + |
| 54 | +function spFatal( $message ) { |
| 55 | + fwrite( STDERR, rtrim( $message ) . "\n" ); |
| 56 | + exit( 1 ); |
| 57 | +} |
| 58 | + |
| 59 | +function spDumpVote( $election, $row ) { |
| 60 | + if ( $election->cbdata['header'] ) { |
| 61 | + echo $election->cbdata['header']; |
| 62 | + $election->cbdata['header'] = false; |
| 63 | + } |
| 64 | + fwrite( $election->cbdata['outFile'], "<vote>" . $row->vote_record . "</vote>\n" ); |
| 65 | +} |
| 66 | + |
Property changes on: trunk/extensions/SecurePoll/cli/dump.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 67 | + native |
Index: trunk/extensions/SecurePoll/cli/tally.php |
— | — | @@ -0,0 +1,107 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Tally an election from a dump file or local database. |
| 6 | + * |
| 7 | + * Can be used to tally very large numbers of votes, when the web interface is |
| 8 | + * not feasible. |
| 9 | + * |
| 10 | + * TODO: The entity classes need a bit of refactoring so that they can operate on |
| 11 | + * a dump file without having to import it into the database. This will avoid |
| 12 | + * some nasty ID collision issues with the import approach. |
| 13 | + */ |
| 14 | + |
| 15 | +$optionsWithArgs = array( 'name' ); |
| 16 | +require( dirname(__FILE__).'/cli.inc' ); |
| 17 | + |
| 18 | +$usage = <<<EOT |
| 19 | +Usage: |
| 20 | + php tally.php [--html] --name <election name> |
| 21 | + php tally.php [--html] <dump file> |
| 22 | + |
| 23 | +EOT; |
| 24 | + |
| 25 | +if ( !isset( $options['name'] ) && isset( $args[0] ) ) { |
| 26 | + echo "Dump files are not supported yet.\n"; |
| 27 | + exit( 1 ); |
| 28 | +} elseif( !isset( $options['name'] ) ) { |
| 29 | + echo $usage; |
| 30 | + exit( 1 ); |
| 31 | +} |
| 32 | + |
| 33 | +if ( !class_exists( 'SecurePoll' ) ) { |
| 34 | + # Uninstalled mode |
| 35 | + # This may actually work some day, for now it will just give you DB errors |
| 36 | + require( dirname( __FILE__ ) . '/../SecurePoll.php' ); |
| 37 | +} |
| 38 | +$election = SecurePoll::getElectionByTitle( $options['name'] ); |
| 39 | +if ( !$election ) { |
| 40 | + echo "The specified election does not exist.\n"; |
| 41 | + exit( 1 ); |
| 42 | +} |
| 43 | +$election = SecurePoll::getElection( $eid ); |
| 44 | +spTallyLocal( $election ); |
| 45 | + |
| 46 | +function spTallyLocal( $election ) { |
| 47 | + $dbr = wfGetDB( DB_SLAVE ); |
| 48 | + $startId = 0; |
| 49 | + $crypt = $election->getCrypt(); |
| 50 | + $ballot = $election->getBallot(); |
| 51 | + $questions = $election->getQuestions(); |
| 52 | + $talliers = $election->getTalliers(); |
| 53 | + |
| 54 | + while ( true ) { |
| 55 | + $res = $dbr->select( |
| 56 | + 'securepoll_votes', |
| 57 | + array( 'vote_id', 'vote_record' ), |
| 58 | + array( |
| 59 | + 'vote_election' => $election->getId(), |
| 60 | + 'vote_current' => 1, |
| 61 | + 'vote_struck' => 0, |
| 62 | + 'vote_id > ' . $dbr->addQuotes( $startId ) |
| 63 | + ), __METHOD__, |
| 64 | + array( 'LIMIT' => 100, 'ORDER BY' => 'vote_id' ) |
| 65 | + ); |
| 66 | + if ( !$res->numRows() ) { |
| 67 | + break; |
| 68 | + } |
| 69 | + foreach ( $res as $row ) { |
| 70 | + var_dump( $row ); |
| 71 | + $startId = $row->vote_id; |
| 72 | + $record = $row->vote_record; |
| 73 | + if ( $crypt ) { |
| 74 | + $status = $crypt->decrypt( $record ); |
| 75 | + if ( !$status->isOK() ) { |
| 76 | + echo $status->getWikiText() . "\n"; |
| 77 | + return; |
| 78 | + } |
| 79 | + $record = $status->value; |
| 80 | + } |
| 81 | + $record = rtrim( $record ); |
| 82 | + $scores = $ballot->unpackRecord( $record ); |
| 83 | + foreach ( $questions as $question ) { |
| 84 | + $qid = $question->getId(); |
| 85 | + if ( !isset( $scores[$qid] ) ) { |
| 86 | + echo wfMsg( 'securepoll-tally-error' ) . "\n"; |
| 87 | + return; |
| 88 | + } |
| 89 | + if ( !$talliers[$qid]->addVote( $scores[$qid] ) ) { |
| 90 | + echo wfMsg( 'securepoll-tally-error' ) . "\n"; |
| 91 | + return; |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + $first = true; |
| 97 | + foreach ( $questions as $question ) { |
| 98 | + if ( $first ) { |
| 99 | + $first = false; |
| 100 | + } else { |
| 101 | + echo "\n"; |
| 102 | + } |
| 103 | + $tallier = $talliers[$question->getId()]; |
| 104 | + $tallier->finishTally(); |
| 105 | + echo $question->getMessage( 'text' ) . "\n" . |
| 106 | + $tallier->getTextResult(); |
| 107 | + } |
| 108 | +} |
Property changes on: trunk/extensions/SecurePoll/cli/tally.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 109 | + native |
Index: trunk/extensions/SecurePoll/cli/cli.inc |
— | — | @@ -0,0 +1,16 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +$IP = getenv( 'MW_INSTALL_PATH' ); |
| 5 | +if ( strval( $IP ) === '' ) { |
| 6 | + $IP = dirname( __FILE__ ).'/../../..'; |
| 7 | +} |
| 8 | +if ( !file_exists( "$IP/includes/WebStart.php" ) ) { |
| 9 | + $IP .= '/phase3'; |
| 10 | +} |
| 11 | +if ( !file_exists( "$IP/includes/WebStart.php" ) ) { |
| 12 | + echo "Can't find your MediaWiki installation. Please set the MW_INSTALL_PATH environment variable.\n"; |
| 13 | + exit( 1 ); |
| 14 | +} |
| 15 | + |
| 16 | +require( $IP . '/maintenance/commandLine.inc' ); |
| 17 | + |
Property changes on: trunk/extensions/SecurePoll/cli/cli.inc |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 18 | + native |