r53668 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r53667‎ | r53668 | r53669 >
Date:07:03, 23 July 2009
Author:tstarling
Status:deferred
Tags:
Comment:
* Added command-line script for XML import. Can do message updates per bug 18975 and bug 18976, as well as initial election configuration.
* Extended dump.php and the XML generation backend to allow configuration of jump wikis, dumps of message text (per bug 18976) and transfer of election configuration.
Modified paths:
  • /trunk/extensions/SecurePoll/SecurePoll.sql (modified) (history)
  • /trunk/extensions/SecurePoll/cli/dump.php (modified) (history)
  • /trunk/extensions/SecurePoll/cli/import.php (added) (history)
  • /trunk/extensions/SecurePoll/includes/Context.php (modified) (history)
  • /trunk/extensions/SecurePoll/includes/Election.php (modified) (history)
  • /trunk/extensions/SecurePoll/includes/Entity.php (modified) (history)
  • /trunk/extensions/SecurePoll/includes/Question.php (modified) (history)
  • /trunk/extensions/SecurePoll/includes/Store.php (modified) (history)

Diff [purge]

Index: trunk/extensions/SecurePoll/includes/Store.php
@@ -22,6 +22,12 @@
2323 function getMessages( $lang, $ids );
2424
2525 /**
 26+ * Get a list of languages that the given entity IDs have messages for.
 27+ * Returns an array of language codes.
 28+ */
 29+ function getLangList( $ids );
 30+
 31+ /**
2632 * Get an array of properties for a given set of IDs. Returns a 2-d array
2733 * mapping IDs and property keys to values.
2834 */
@@ -84,6 +90,22 @@
8591 return $messages;
8692 }
8793
 94+ function getLangList( $ids ) {
 95+ $db = $this->getDB();
 96+ $res = $db->select(
 97+ 'securepoll_msgs',
 98+ 'DISTINCT msg_lang',
 99+ array(
 100+ 'msg_entity' => $ids
 101+ ),
 102+ __METHOD__ );
 103+ $langs = array();
 104+ foreach ( $res as $row ) {
 105+ $langs[] = $row->msg_lang;
 106+ }
 107+ return $langs;
 108+ }
 109+
88110 function getProperties( $ids ) {
89111 $db = $this->getDB();
90112 $res = $db->select(
@@ -237,6 +259,19 @@
238260 return array_intersect_key( $this->messages[$lang], array_flip( $ids ) );
239261 }
240262
 263+ function getLangList( $ids ) {
 264+ $langs = array();
 265+ foreach ( $this->messages as $lang => $langMessages ) {
 266+ foreach ( $ids as $id ) {
 267+ if ( isset( $langMessages[$id] ) ) {
 268+ $langs[] = $lang;
 269+ break;
 270+ }
 271+ }
 272+ }
 273+ return $langs;
 274+ }
 275+
241276 function getProperties( $ids ) {
242277 $ids = (array)$ids;
243278 return array_intersect_key( $this->properties, array_flip( $ids ) );
Index: trunk/extensions/SecurePoll/includes/Election.php
@@ -309,7 +309,7 @@
310310 /**
311311 * Get an XML snippet describing the configuration of this object
312312 */
313 - function getConfXml() {
 313+ function getConfXml( $options = array() ) {
314314 $s = "<configuration>\n" .
315315 Xml::element( 'title', array(), $this->title ) . "\n" .
316316 Xml::element( 'ballot', array(), $this->ballotType ) . "\n" .
@@ -317,10 +317,27 @@
318318 Xml::element( 'primaryLang', array(), $this->primaryLang ) . "\n" .
319319 Xml::element( 'startDate', array(), wfTimestamp( TS_ISO_8601, $this->startDate ) ) . "\n" .
320320 Xml::element( 'endDate', array(), wfTimestamp( TS_ISO_8601, $this->endDate ) ) . "\n" .
321 - Xml::element( 'auth', array(), $this->authType ) . "\n" .
322 - $this->getConfXmlEntityStuff();
 321+ $this->getConfXmlEntityStuff( $options );
 322+
 323+ # If we're making a jump dump, we need to add some extra properties, and
 324+ # override the auth type
 325+ if ( !empty( $options['jump'] ) ) {
 326+ $s .=
 327+ Xml::element( 'auth', array(), 'local' ) . "\n" .
 328+ Xml::element( 'property',
 329+ array( 'name' => 'jump-url' ),
 330+ $this->context->getSpecialTitle()->getFullURL()
 331+ ) . "\n" .
 332+ Xml::element( 'property',
 333+ array( 'name' => 'jump-id' ),
 334+ $this->getId()
 335+ ) . "\n";
 336+ } else {
 337+ $s .= Xml::element( 'auth', array(), $this->authType ) . "\n";
 338+ }
 339+
323340 foreach ( $this->getQuestions() as $question ) {
324 - $s .= $question->getConfXml();
 341+ $s .= $question->getConfXml( $options );
325342 }
326343 $s .= "</configuration>\n";
327344 return $s;
Index: trunk/extensions/SecurePoll/includes/Question.php
@@ -38,10 +38,10 @@
3939 return $this->options;
4040 }
4141
42 - function getConfXml() {
43 - $s = "<question>\n" . $this->getConfXmlEntityStuff();
 42+ function getConfXml( $options = array() ) {
 43+ $s = "<question>\n" . $this->getConfXmlEntityStuff( $options );
4444 foreach ( $this->getOptions() as $option ) {
45 - $s .= $option->getConfXml();
 45+ $s .= $option->getConfXml( $options );
4646 }
4747 $s .= "</question>\n";
4848 return $s;
Index: trunk/extensions/SecurePoll/includes/Entity.php
@@ -168,6 +168,18 @@
169169 }
170170
171171 /**
 172+ * Get a list of languages for which we have translations, for this entity
 173+ * and its descendants.
 174+ */
 175+ function getLangList() {
 176+ $ids = array( $this->getId() );
 177+ foreach ( $this->getDescendants() as $child ) {
 178+ $ids[] = $child->getId();
 179+ }
 180+ return $this->context->getStore()->getLangList( $ids );
 181+ }
 182+
 183+ /**
172184 * Get a property value. If it does not exist, the $default parameter
173185 * is passed back.
174186 * @param $name string
@@ -197,24 +209,35 @@
198210 /**
199211 * Get configuration XML. Overridden by most subclasses.
200212 */
201 - function getConfXml() {
 213+ function getConfXml( $options = array() ) {
202214 return "<{$this->type}>\n" .
203 - $this->getConfXmlEntityStuff() .
 215+ $this->getConfXmlEntityStuff( $options ) .
204216 "</{$this->type}>\n";
205217 }
206218
207219 /**
208220 * Get an XML snippet giving the messages and properties
209221 */
210 - function getConfXmlEntityStuff() {
 222+ function getConfXmlEntityStuff( $options = array() ) {
211223 $s = Xml::element( 'id', array(), $this->getId() ) . "\n";
212224 foreach ( $this->getAllProperties() as $name => $value ) {
213225 $s .= Xml::element( 'property', array( 'name' => $name ), $value ) . "\n";
214226 }
 227+ if ( isset( $options['langs'] ) ) {
 228+ $langs = $options['langs'];
 229+ } else {
 230+ $langs = $this->context->languages;
 231+ }
215232 foreach ( $this->getMessageNames() as $name ) {
216 - foreach ( $this->context->languages as $lang ) {
217 - $s .= Xml::element( 'message', array( 'name' => $name, 'lang' => $lang ),
218 - $this->getRawMessage( $name, $lang ) ) . "\n";
 233+ foreach ( $langs as $lang ) {
 234+ $value = $this->getRawMessage( $name, $lang );
 235+ if ( $value !== false ) {
 236+ $s .= Xml::element(
 237+ 'message',
 238+ array( 'name' => $name, 'lang' => $lang ),
 239+ $value
 240+ ) . "\n";
 241+ }
219242 }
220243 }
221244 return $s;
Index: trunk/extensions/SecurePoll/includes/Context.php
@@ -74,6 +74,11 @@
7575 return $this->store;
7676 }
7777
 78+ /** Get a Title object for Special:SecurePoll */
 79+ function getSpecialTitle( $subpage = false ) {
 80+ return SpecialPage::getTitleFor( 'SecurePoll', $subpage );
 81+ }
 82+
7883 /** Set the store class */
7984 function setStoreClass( $class ) {
8085 $this->store = null;
Index: trunk/extensions/SecurePoll/cli/import.php
@@ -0,0 +1,257 @@
 2+<?php
 3+
 4+require( dirname( __FILE__ ) . '/cli.inc' );
 5+
 6+$usage = <<<EOT
 7+Import configuration files into the local SecurePoll database. Files can be
 8+generated with dump.php.
 9+
 10+Usage: import.php [options] <file>
 11+
 12+Options are:
 13+ --update-msgs Update the internationalised text for the elections, do
 14+ not update configuration.
 15+
 16+ --replace If an election with a conflicting title exists already,
 17+ replace it, updating its configuration. The default is
 18+ to exit with an error.
 19+
 20+Note that any vote records will NOT be imported.
 21+
 22+For the moment, the entity IDs are preserved, to allow easier implementation of
 23+the message update feature. This means conflicting entity IDs in the local
 24+database will generate an error. This restriction will be removed in the
 25+future.
 26+
 27+EOT;
 28+
 29+# Most of the code here will eventually be refactored into the update interfaces
 30+# of the entity and context classes, but that project can wait until we have a
 31+# setup UI.
 32+
 33+if ( !isset( $args[0] ) ) {
 34+ echo $usage;
 35+ exit( 1 );
 36+}
 37+if ( !file_exists( $args[0] ) ) {
 38+ echo "The specified file \"{$args[0]}\" does not exist\n";
 39+ exit( 1 );
 40+}
 41+
 42+foreach ( array( 'update-msgs', 'replace' ) as $optName ) {
 43+ if ( !isset( $options[$optName] ) ) {
 44+ $options[$optName] = false;
 45+ }
 46+}
 47+
 48+$success = spImportDump( $args[0], $options );
 49+exit( $success ? 0 : 1 );
 50+
 51+function spImportDump( $fileName, $options ) {
 52+ $store = new SecurePoll_XMLStore( $fileName );
 53+ $success = $store->readFile();
 54+ if ( !$success ) {
 55+ echo "Error reading XML dump, possibly corrupt\n";
 56+ return false;
 57+ }
 58+ $electionIds = $store->getAllElectionIds();
 59+ if ( !count( $electionIds ) ) {
 60+ echo "No elections found to import.\n";
 61+ return true;
 62+ }
 63+
 64+ $xc = new SecurePoll_Context;
 65+ $xc->setStore( $store );
 66+ $dbw = wfGetDB( DB_MASTER );
 67+
 68+ # Start the configuration transaction
 69+ $dbw->begin();
 70+ foreach ( $electionIds as $id ) {
 71+ $elections = $store->getElectionInfo( array( $id ) );
 72+ $electionInfo = reset( $elections );
 73+ $existingId = $dbw->selectField(
 74+ 'securepoll_elections',
 75+ 'el_entity',
 76+ array( 'el_title' => $electionInfo['title'] ),
 77+ __METHOD__,
 78+ array( 'FOR UPDATE' ) );
 79+ if ( $existingId !== false ) {
 80+ if ( $options['replace'] ) {
 81+ spDeleteElection( $existingId );
 82+ $success = spImportConfiguration( $store, $electionInfo );
 83+ } elseif ( $options['update-msgs'] ) {
 84+ # Do the message update and move on to the next election
 85+ $success = spUpdateMessages( $store, $electionInfo );
 86+ } else {
 87+ echo "Conflicting election title found \"{$electionInfo['title']}\"\n";
 88+ echo "Use --replace to replace the existing election.\n";
 89+ $success = false;
 90+ }
 91+ } elseif ( $options['update-msgs'] ) {
 92+ echo "Cannot update messages: election \"{$electionInfo['title']}\" not found.\n";
 93+ echo "Import the configuration first, without the --update-msgs switch.\n";
 94+ $success = false;
 95+ } else {
 96+ $success = spImportConfiguration( $store, $electionInfo );
 97+ }
 98+ if ( !$success ) {
 99+ $dbw->rollback();
 100+ return false;
 101+ }
 102+ }
 103+ $dbw->commit();
 104+ return true;
 105+}
 106+
 107+function spDeleteElection( $electionId ) {
 108+ $dbw = wfGetDB( DB_MASTER );
 109+
 110+ # Get a list of entity IDs and lock them
 111+ $questionIds = array();
 112+ $res = $dbw->select( 'securepoll_questions', array( 'qu_entity' ),
 113+ array( 'qu_election' => $electionId ),
 114+ __METHOD__, array( 'FOR UPDATE' ) );
 115+ foreach ( $res as $row ) {
 116+ $questionIds[] = $row->qu_entity;
 117+ }
 118+
 119+ $res = $dbw->select( 'securepoll_options', array( 'op_entity' ),
 120+ array( 'op_election' => $electionId ),
 121+ __METHOD__, array( 'FOR UPDATE' ) );
 122+ $optionIds = array();
 123+ foreach ( $res as $row ) {
 124+ $optionIds[] = $row->op_entity;
 125+ }
 126+
 127+ $entityIds = array_merge( $optionIds, $questionIds, array( $electionId ) );
 128+
 129+ # Delete the messages and properties
 130+ $dbw->delete( 'securepoll_msgs', array( 'msg_entity' => $entityIds ) );
 131+ $dbw->delete( 'securepoll_properties', array( 'pr_entity' => $entityIds ) );
 132+
 133+ # Delete the entities
 134+ $dbw->delete( 'securepoll_options', array( 'op_entity' => $optionIds ), __METHOD__ );
 135+ $dbw->delete( 'securepoll_questions', array( 'qu_entity' => $questionIds ), __METHOD__ );
 136+ $dbw->delete( 'securepoll_elections', array( 'el_entity' => $electionId ), __METHOD__ );
 137+ $dbw->delete( 'securepoll_entity', array( 'en_id' => $entityIds ), __METHOD__ );
 138+}
 139+
 140+function spInsertEntity( $type, $id ) {
 141+ $dbw = wfGetDB( DB_MASTER );
 142+ $dbw->insert( 'securepoll_entity',
 143+ array(
 144+ 'en_id' => $id,
 145+ 'en_type' => $type,
 146+ ),
 147+ __METHOD__
 148+ );
 149+}
 150+
 151+function spImportConfiguration( $store, $electionInfo ) {
 152+ $dbw = wfGetDB( DB_MASTER );
 153+ $sourceIds = array();
 154+
 155+ # Election
 156+ spInsertEntity( 'election', $electionInfo['id'] );
 157+ $dbw->insert( 'securepoll_elections',
 158+ array(
 159+ 'el_entity' => $electionInfo['id'],
 160+ 'el_title' => $electionInfo['title'],
 161+ 'el_ballot' => $electionInfo['ballot'],
 162+ 'el_tally' => $electionInfo['tally'],
 163+ 'el_primary_lang' => $electionInfo['primaryLang'],
 164+ 'el_start_date' => $electionInfo['startDate'],
 165+ 'el_end_date' => $electionInfo['endDate'],
 166+ 'el_auth_type' => $electionInfo['auth']
 167+ ),
 168+ __METHOD__ );
 169+ $sourceIds[] = $electionInfo['id'];
 170+
 171+
 172+ # Questions
 173+ $index = 1;
 174+ foreach ( $electionInfo['questions'] as $questionInfo ) {
 175+ spInsertEntity( 'question', $questionInfo['id'] );
 176+ $dbw->insert( 'securepoll_questions',
 177+ array(
 178+ 'qu_entity' => $questionInfo['id'],
 179+ 'qu_election' => $electionInfo['id'],
 180+ 'qu_index' => $index++,
 181+ ),
 182+ __METHOD__ );
 183+ $sourceIds[] = $questionInfo['id'];
 184+
 185+ # Options
 186+ $insertBatch = array();
 187+ foreach ( $questionInfo['options'] as $optionInfo ) {
 188+ spInsertEntity( 'option', $optionInfo['id'] );
 189+ $insertBatch[] = array(
 190+ 'op_entity' => $optionInfo['id'],
 191+ 'op_election' => $electionInfo['id'],
 192+ 'op_question' => $questionInfo['id']
 193+ );
 194+ $sourceIds[] = $optionInfo['id'];
 195+ }
 196+ $dbw->insert( 'securepoll_options', $insertBatch, __METHOD__ );
 197+ }
 198+
 199+ # Messages
 200+ spInsertMessages( $store, $sourceIds );
 201+
 202+ # Properties
 203+ $properties = $store->getProperties( $sourceIds );
 204+ $insertBatch = array();
 205+ foreach ( $properties as $id => $entityProps ) {
 206+ foreach ( $entityProps as $key => $value ) {
 207+ $insertBatch[] = array(
 208+ 'pr_entity' => $id,
 209+ 'pr_key' => $key,
 210+ 'pr_value' => $value
 211+ );
 212+ }
 213+ }
 214+ if ( $insertBatch ) {
 215+ $dbw->insert( 'securepoll_properties', $insertBatch, __METHOD__ );
 216+ }
 217+ return true;
 218+}
 219+
 220+function spInsertMessages( $store, $entityIds ) {
 221+ $langs = $store->getLangList( $entityIds );
 222+ $insertBatch = array();
 223+ foreach ( $langs as $lang ) {
 224+ $messages = $store->getMessages( $lang, $entityIds );
 225+ foreach ( $messages as $id => $entityMsgs ) {
 226+ foreach ( $entityMsgs as $key => $text ) {
 227+ $insertBatch[] = array(
 228+ 'msg_entity' => $id,
 229+ 'msg_lang' => $lang,
 230+ 'msg_key' => $key,
 231+ 'msg_text' => $text
 232+ );
 233+ }
 234+ }
 235+ }
 236+ if ( $insertBatch ) {
 237+ $dbw = wfGetDB( DB_MASTER );
 238+ $dbw->insert( 'securepoll_msgs', $insertBatch, __METHOD__ );
 239+ }
 240+}
 241+
 242+function spUpdateMessages( $store, $electionInfo ) {
 243+ $entityIds = array( $electionInfo['id'] );
 244+ foreach ( $electionInfo['questions'] as $questionInfo ) {
 245+ $entityIds[] = $questionInfo['id'];
 246+ foreach ( $questionInfo['options'] as $optionInfo ) {
 247+ $entityIds[] = $optionInfo['id'];
 248+ }
 249+ }
 250+
 251+ # Delete existing messages
 252+ $dbw = wfGetDB( DB_MASTER );
 253+ $dbw->delete( 'securepoll_msgs', array( 'msg_entity' => $entityIds ), __METHOD__ );
 254+
 255+ # Insert new messages
 256+ spInsertMessages( $store, $entityIds );
 257+}
 258+
Property changes on: trunk/extensions/SecurePoll/cli/import.php
___________________________________________________________________
Added: svn:eol-style
1259 + native
Index: trunk/extensions/SecurePoll/cli/dump.php
@@ -8,6 +8,15 @@
99 $optionsWithArgs = array( 'o' );
1010 require( dirname(__FILE__).'/cli.inc' );
1111
 12+$usage = <<<EOT
 13+Usage: php dump.php [options...] <election name>
 14+Options:
 15+ -o <outfile> Output to the specified file
 16+ --votes Include vote records
 17+ --all-langs Include messages for all languages instead of just the primary
 18+ --jump Produce a configuration dump suitable for setting up a jump wiki
 19+EOT;
 20+
1221 if ( !isset( $args[0] ) ) {
1322 spFatal( "Usage: php dump.php [-o <outfile>] <election name>" );
1423 }
@@ -32,18 +41,28 @@
3342 spFatal( "Unable to open $fileName for writing" );
3443 }
3544
36 -$context->setLanguages( array( $election->getLanguage() ) );
 45+if ( isset( $options['all-langs'] ) ) {
 46+ $langs = $election->getLangList();
 47+} else {
 48+ $langs = array( $election->getLanguage() );
 49+}
 50+$confXml = $election->getConfXml( array(
 51+ 'jump' => isset( $options['jump'] ),
 52+ 'langs' => $langs
 53+) );
3754
3855 $cbdata = array(
39 - 'header' => "<SecurePoll>\n<election>\n" . $election->getConfXml(),
 56+ 'header' => "<SecurePoll>\n<election>\n$confXml",
4057 'outFile' => $outFile
4158 );
 59+$election->cbdata = $cbdata;
4260
4361 # Write vote records
44 -$election->cbdata = $cbdata;
45 -$status = $election->dumpVotesToCallback( 'spDumpVote' );
46 -if ( !$status->isOK() ) {
47 - spFatal( $status->getWikiText() );
 62+if ( isset( $options['votes'] ) ) {
 63+ $status = $election->dumpVotesToCallback( 'spDumpVote' );
 64+ if ( !$status->isOK() ) {
 65+ spFatal( $status->getWikiText() );
 66+ }
4867 }
4968 if ( $election->cbdata['header'] ) {
5069 fwrite( $outFile, $election->cbdata['header'] );
Index: trunk/extensions/SecurePoll/SecurePoll.sql
@@ -89,6 +89,7 @@
9090
9191
9292 -- Options for answering a given question, see Option.php
 93+-- FIXME: needs op_election index for import.php
9394 CREATE TABLE /*_*/securepoll_options (
9495 -- securepoll_entity.en_id
9596 op_entity int not null primary key,
@@ -99,6 +100,7 @@
100101 ) /*$wgDBTableOptions*/;
101102 CREATE INDEX /*i*/spop_question ON /*_*/securepoll_options (op_question, op_entity);
102103
 104+
103105 -- Voter list, independent for each election
104106 -- See Voter.php
105107 CREATE TABLE /*_*/securepoll_voters (

Status & tagging log