r99322 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r99321‎ | r99322 | r99323 >
Date:19:41, 8 October 2011
Author:questpc
Status:deferred
Tags:
Comment:
Structured answer can be displayed as table at poll page / Special:Pollresults page. Export of structured answer into XLS format. PEAR Excel wrapper improvements.
Modified paths:
  • /trunk/extensions/QPoll/clientside/qp_results.css (modified) (history)
  • /trunk/extensions/QPoll/clientside/qp_user.css (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_interpresult.php (modified) (history)
  • /trunk/extensions/QPoll/i18n/qp.i18n.php (modified) (history)
  • /trunk/extensions/QPoll/includes/qp_excel.php (modified) (history)
  • /trunk/extensions/QPoll/model/qp_pollstore.php (modified) (history)
  • /trunk/extensions/QPoll/model/qp_questiondata.php (modified) (history)
  • /trunk/extensions/QPoll/qp_user.php (modified) (history)
  • /trunk/extensions/QPoll/specials/qp_results.php (modified) (history)
  • /trunk/extensions/QPoll/view/poll/qp_pollview.php (modified) (history)
  • /trunk/extensions/QPoll/view/qp_interpresultview.php (modified) (history)
  • /trunk/extensions/QPoll/view/results/qp_questiondataresults.php (modified) (history)
  • /trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php (modified) (history)

Diff [purge]

Index: trunk/extensions/QPoll/i18n/qp.i18n.php
@@ -60,6 +60,7 @@
6161 'qp_users_list' => 'List all users',
6262 'qp_browse_to_poll' => 'Browse to $1',
6363 'qp_browse_to_user' => 'Browse to $1',
 64+ 'qp_browse_to_interpretation' => 'Browse to $1',
6465 'qp_votes_count' => '$1 {{PLURAL:$1|vote|votes}}',
6566 'qp_source_link' => 'Source',
6667 'qp_stats_link' => 'Statistics',
@@ -79,12 +80,14 @@
8081 'qp_results_line_qucl' => '$1: $2 $3',
8182 'qp_results_submit_attempts' => 'Submit attempts: $1',
8283 'qp_results_interpretation_header' => 'Answer interpretation',
83 - 'qp_results_short_interpretation' => 'Short interpretation: $1',
84 - 'qp_results_long_interpretation' => 'Long interpretation: $1',
 84+ 'qp_results_short_interpretation' => 'Short interpretation',
 85+ 'qp_results_long_interpretation' => 'Long interpretation',
 86+ 'qp_results_structured_interpretation' => 'Structured interpretation',
8587 'qp_poll_has_no_interpretation' => 'This poll has no interpretation template defined in the poll\'s header.',
8688 'qp_interpetation_wrong_answer' => 'Wrong answer',
8789 'qp_export_to_xls' => 'Export statistics into XLS format',
8890 'qp_voices_to_xls' => 'Export voices into XLS format',
 91+ 'qp_interpretation_results_to_xls' => 'Export answer interpretations into XLS format',
8992 'qp_users_answered_questions' => '$1 {{PLURAL:$1|user|users}} answered to the questions',
9093 'qp_func_no_such_poll' => 'No such poll ($1)',
9194 'qp_func_missing_question_id' => 'Please specify an existing question id (starting from 1) for the poll $1.',
@@ -177,10 +180,6 @@
178181 * $6 is a link to the with link label {{msg-mw|qp_not_participated_link}}',
179182 'qp_results_submit_attempts' => 'Parameters:
180183 * $1 is the number of submit attempts',
181 - 'qp_results_short_interpretation' => 'Parameters:
182 -* $1 is a piece of free text.',
183 - 'qp_results_long_interpretation' => 'Parameters:
184 -* $1 is a piece of free text.',
185184 'qp_error_missed_dependance_poll' => 'Parameters:
186185 * $1 is the poll ID of the poll having an error.
187186 * $2 is a link to the page with the poll, that this erroneous poll depends on.
@@ -2594,6 +2593,7 @@
25952594 'qp_users_list' => 'Список всех участников',
25962595 'qp_browse_to_poll' => 'Перейти к $1',
25972596 'qp_browse_to_user' => 'Перейти к $1',
 2597+ 'qp_browse_to_interpretation' => 'Перейти к $1',
25982598 'qp_votes_count' => '$1 {{PLURAL:$1|голос|голоса|голосов}}',
25992599 'qp_source_link' => 'Исходный код',
26002600 'qp_stats_link' => 'Статистика',
@@ -2613,12 +2613,14 @@
26142614 'qp_results_line_qucl' => '$1: $2 $3',
26152615 'qp_results_submit_attempts' => 'Попыток ответа: $1',
26162616 'qp_results_interpretation_header' => 'Интерпретация ответа',
2617 - 'qp_results_short_interpretation' => 'Краткая интерпретация: $1',
2618 - 'qp_results_long_interpretation' => 'Подробная интерпретация: $1',
 2617+ 'qp_results_short_interpretation' => 'Краткая интерпретация',
 2618+ 'qp_results_long_interpretation' => 'Подробная интерпретация',
 2619+ 'qp_results_structured_interpretation' => 'Структурная интерпретация',
26192620 'qp_poll_has_no_interpretation' => 'Шаблон, используемый для интерпретации результатов опроса, не определен в заголовке опроса',
26202621 'qp_interpetation_wrong_answer' => 'Неправильный ответ',
26212622 'qp_export_to_xls' => 'Экспортировать статистику в XLS формате',
26222623 'qp_voices_to_xls' => 'Экспортировать голоса в XLS формате',
 2624+ 'qp_interpretation_results_to_xls' => 'Экспортировать интерпретации ответов в XLS формате',
26232625 'qp_users_answered_questions' => 'На вопросы {{PLURAL:$1|ответил $1 участник|ответило $1 участника|ответили $1 участников}}',
26242626 'qp_func_no_such_poll' => 'Опрос не найден ($1)',
26252627 'qp_func_missing_question_id' => 'Укажите существующий идентификатор вопроса (начинающийся с единицы) для опроса $1',
Index: trunk/extensions/QPoll/clientside/qp_user.css
@@ -34,11 +34,14 @@
3535 /* Part for the inputfields */
3636 .qpoll a.input, .qpoll a.input:hover, .qpoll a.input:active, .qpoll a.input:visited { text-decoration:none; color:black; outline: 0 }
3737 .qpoll a.input span { outline:#7F9DB9 solid 1px; border:1px solid #7F9DB9; } /* *border is for IE6/7 star is removed due to ff console error */
38 -.qpoll .interp_error { border: 2px solid gray; padding: 0.5em; color: white; background-color: red; }
39 -.qpoll .interp_answer { border: 2px solid gray; padding: 0.5em; color: black; background-color: lightblue; }
4038 .qpoll .cat_prefilled { color: blue; }
4139 .qpoll .cat_noanswer { background-color: LightYellow; }
4240 .qpoll .cat_error { background-color: LightYellow; border: 1px solid gray; }
 41+.qpoll .interp_error { border: 2px solid gray; padding: 0.5em; color: white; background-color: red; margin: 0.3em 0 0.3em 0; }
 42+.qpoll .interp_answer { border: 2px solid gray; padding: 0.5em; color: black; background-color: lightblue; margin: 0.3em 0 0.3em 0; }
 43+.qpoll table.structured_answer { border-collapse: collapse; margin: 0.3em 0 0.3em 0; }
 44+.qpoll table.structured_answer th { text-align:left; border-top:1px solid gray; border-left:1px solid gray; border-right:1px solid gray; background-color: Moccasin; padding: 0 0.2em 0 0.2em; }
 45+.qpoll table.structured_answer td { border-bottom:1px solid gray; border-left:1px solid gray; border-right:1px solid gray; background-color: Azure; padding: 0 0.2em 0 0.2em; }
4346
4447 /* script view */
4548 .qpoll .line_numbers { font-family: monospace, "Courier New"; white-space:pre; color:green; background-color: lightgray; float:left; border-left: 1px solid darkgray; border-top: 1px solid darkgray; border-bottom: 1px solid darkgray; padding: 0.5em; }
Index: trunk/extensions/QPoll/clientside/qp_results.css
@@ -9,5 +9,9 @@
1010 .qpoll table.qdata tr.qdatatext td { text-align: left; }
1111 .qpoll .cat_part { background-color: Lightyellow; border: 1px solid gray; padding: 0 0.5em 0 0.5em; }
1212 .qpoll .cat_unknown { color: white; background-color: IndianRed; border: 1px solid gray; padding: 0 0.5em 0 0.5em; }
13 -.qpoll .interp_answer { border: 1px solid gray; padding: 0; margin: 0; color: black; background-color: lightgray; font-weight:bold; line-height:2em; }
 13+.qpoll .interp_header { background-color: lightgray; padding: 0.2em; border: 1px solid gray; margin: 0.3em 0 0.3em 0; }
 14+.qpoll .interp_answer { border: 1px solid gray; padding: 0.2em; margin: 0.3em 0 0.3em 0; color: black; background-color: lightblue; font-weight:600; line-height:2em; }
1415 .qpoll .interp_answer_body { border: none; border-top: 1px solid gray; padding: 0.5em; margin-top: 0; color: black; background-color: Azure; font-weight: normal; line-height:normal; }
 16+.qpoll table.structured_answer { border-collapse: collapse; margin: 0.3em 0 0.3em 0; }
 17+.qpoll table.structured_answer th { text-align:left; border-top:1px solid gray; border-left:1px solid gray; border-right:1px solid gray; background-color: lightgray; padding: 0 0.2em 0 0.2em; }
 18+.qpoll table.structured_answer td { border-bottom:1px solid gray; border-left:1px solid gray; border-right:1px solid gray; background-color: White; padding: 0 0.2em 0 0.2em; }
Index: trunk/extensions/QPoll/model/qp_questiondata.php
@@ -14,6 +14,9 @@
1515 */
1616 class qp_QuestionData {
1717
 18+ # associated view instance (singleton)
 19+ static protected $view;
 20+
1821 // DB index (with current scheme is non-unique)
1922 var $question_id = null;
2023 // common properties
@@ -38,6 +41,7 @@
3942 * another entries of $argv define property names and their values
4043 */
4144 function __construct( $argv ) {
 45+ self::$view = new stdClass;
4246 if ( array_key_exists( 'from', $argv ) ) {
4347 switch ( $argv[ 'from' ] ) {
4448 case 'postdata' :
@@ -66,10 +70,15 @@
6771 }
6872
6973 /**
70 - * Create appropriate view for Special:Pollresults
 74+ * Get appropriate view for Special:Pollresults
7175 */
72 - function createView() {
73 - return new qp_QuestionDataResults( $this );
 76+ function getView() {
 77+ if ( get_class( self::$view ) !== 'qp_QuestionDataResults' ) {
 78+ self::$view = new qp_QuestionDataResults( $this );
 79+ } else {
 80+ self::$view->setController( $this );
 81+ }
 82+ return self::$view;
7483 }
7584
7685 /**
@@ -177,8 +186,16 @@
178187 /**
179188 * Questions of type="text" require a different view logic in Special:Pollresults page
180189 */
181 - function createView() {
182 - return new qp_TextQuestionDataResults( $this );
 190+ /**
 191+ * Get appropriate view for Special:Pollresults
 192+ */
 193+ function getView() {
 194+ if ( get_class( self::$view ) !== 'qp_TextQuestionDataResults' ) {
 195+ self::$view = new qp_TextQuestionDataResults( $this );
 196+ } else {
 197+ self::$view->setController( $this );
 198+ }
 199+ return self::$view;
183200 }
184201
185202 } /* end of qp_TextQuestionData class */
Index: trunk/extensions/QPoll/model/qp_pollstore.php
@@ -95,6 +95,9 @@
9696 $title = $wgParser->getTitle();
9797 }
9898 $this->mArticleId = $title->getArticleID();
 99+ if ( !isset( $argv['poll_id'] ) ) {
 100+ throw new MWException( 'Parameter "from" = poll_get / poll_post requires parameter "poll_id" in ' . __METHOD__ );
 101+ }
99102 $this->mPollId = $argv[ 'poll_id' ];
100103 if ( array_key_exists( 'order_id', $argv ) ) {
101104 $this->mOrderId = $argv[ 'order_id' ];
@@ -125,8 +128,10 @@
126129 $this->setPid();
127130 } else {
128131 $this->loadPid();
129 - if ( is_null( $this->pid ) ) {
 132+ if ( is_null( $this->pid ) &&
 133+ $this->pollDescIsValid() ) {
130134 # try to create poll description (DB state was incomplete)
 135+ # todo: check, whether this is really required for random questions
131136 $this->setPid();
132137 }
133138 }
@@ -153,6 +158,8 @@
154159 $this->randomQuestionCount = $row->random_question_count;
155160 }
156161 break;
 162+ default :
 163+ throw new MWException( 'Unknown value of "from" parameter: ' . $argv[ 'from' ] . ' in ' . __METHOD__ );
157164 }
158165 }
159166 }
@@ -204,6 +211,26 @@
205212 return $this->mPollId;
206213 }
207214
 215+ /**
 216+ * @return boolean true when the current instance has all of the
 217+ * properties matched to 'qp_poll_desc' fields initialized with values
 218+ * taken from $argv[] argument of constructor;
 219+ *
 220+ * when this function returns false, $this->setPid() cannot be called;
 221+ */
 222+ private function pollDescIsValid() {
 223+ # non-checked fields:
 224+ # 'pid' is key (result of insert);
 225+ # 'article_id' is always created by constructor
 226+ # 'poll_id' is a mandatory parameter of constructor
 227+ return
 228+ !is_null( $this->mOrderId ) &&
 229+ !is_null( $this->dependsOn ) &&
 230+ !is_null( $this->interpNS ) &&
 231+ !is_null ( $this->interpDBkey ) &&
 232+ !is_null ( $this->randomQuestionCount );
 233+ }
 234+
208235 # returns Title object, to get a URI path, use Title::getFullText()/getPrefixedText() on it
209236 function getTitle() {
210237 if ( $this->mArticleId === 0 ) {
@@ -286,7 +313,9 @@
287314
288315 /**
289316 * iterates through the list of users who voted the current poll
290 - * @return mixed false on failure, array of (uid=>username) on success (might be empty)
 317+ * @return mixed false on failure, array of
 318+ * ( uid=> ('username'=>username, 'interpretation'=>instanceof qp_InterpResult) ) on success;
 319+ * warning: resulting array might be empty;
291320 */
292321 function pollVotersPager( $offset = 0, $limit = 20 ) {
293322 if ( $this->pid === null ) {
@@ -294,7 +323,7 @@
295324 }
296325 $qp_users_polls = self::$db->tableName( 'qp_users_polls' );
297326 $qp_users = self::$db->tableName( 'qp_users' );
298 - $query = "SELECT qup.uid AS uid, name AS username " .
 327+ $query = "SELECT qup.uid AS uid, name AS username, short_interpretation, long_interpretation, structured_interpretation " .
299328 "FROM $qp_users_polls qup " .
300329 "INNER JOIN $qp_users qu ON qup.uid = qu.uid " .
301330 "WHERE pid = " . intval( $this->pid ) . " " .
@@ -302,7 +331,14 @@
303332 $res = self::$db->query( $query, __METHOD__ );
304333 $result = array();
305334 while ( $row = self::$db->fetchObject( $res ) ) {
306 - $result[intval( $row->uid )] = $row->username;
 335+ $interpResult = new qp_InterpResult();
 336+ $interpResult->short = $row->short_interpretation;
 337+ $interpResult->long = $row->long_interpretation;
 338+ $interpResult->structured = $row->structured_interpretation;
 339+ $result[intval( $row->uid )] = array(
 340+ 'username' => $row->username,
 341+ 'interpretation' => $interpResult
 342+ );
307343 }
308344 return $result;
309345 }
Index: trunk/extensions/QPoll/specials/qp_results.php
@@ -121,15 +121,11 @@
122122 }
123123 break;
124124 case 'stats_xls':
125 - if ( $pid !== null ) {
126 - $pid = intval( $pid );
127 - $this->statsToXLS( $pid );
128 - }
129 - break;
130125 case 'voices_xls':
 126+ case 'interpretation_xls':
131127 if ( $pid !== null ) {
132128 $pid = intval( $pid );
133 - $this->voicesToXLS( $pid );
 129+ $this->writeXLS( $cmd, $pid );
134130 }
135131 break;
136132 case 'uvote':
@@ -181,25 +177,21 @@
182178 $wgOut->addHTML( $output . '</div>' );
183179 }
184180
185 - private function showAnswerHeader( qp_PollStore $pollStore ) {
186 - $out = '<div style="font-weight:bold;">' . wfMsg( 'qp_results_submit_attempts', intval( $pollStore->attempts ) ) . '</div>';
 181+ private function getAnswerHeader( qp_PollStore $pollStore ) {
 182+ $tags = array(
 183+ array( '__tag' => 'div', 'style' => 'font-weight:bold;', wfMsg( 'qp_results_submit_attempts', intval( $pollStore->attempts ) ) )
 184+ );
187185 $interpTitle = $pollStore->getInterpTitle();
188186 if ( $interpTitle === null ) {
189 - $out .= wfMsg( 'qp_poll_has_no_interpretation' );
190 - return $out;
 187+ $tags[] = wfMsg( 'qp_poll_has_no_interpretation' );
 188+ return $tags;
191189 }
192 - /*
193 - # currently, error is not stored in DB, only the vote and long / short interpretations
194 - # todo: is it worth to store it?
195 - if ( $pollStore->interpResult->error != '' ) {
196 - return '<strong class="error">' . qp_Setup::specialchars( $pollStore->interpResult->error ) . '</strong>';
197 - }
198 - */
199 - $out .= '<div class="interp_answer">' . wfMsg( 'qp_results_interpretation_header' ) .
200 - '<div class="interp_answer_body">' . nl2br( wfMsg( 'qp_results_short_interpretation', qp_Setup::specialChars( $pollStore->interpResult->short ) ) ) . '</div>' .
201 - '<div class="interp_answer_body">' . nl2br( wfMsg( 'qp_results_long_interpretation', qp_Setup::specialChars( $pollStore->interpResult->long ) ) ) . '</div>' .
202 - '</div>';
203 - return $out;
 190+ # 'parentheses' key is unavailable in MediaWiki 1.15.x
 191+ $interp_link = $this->qpLink( $interpTitle, $interpTitle->getPrefixedText() );
 192+ $tags[] = wfMsg( 'qp_browse_to_interpretation', $interp_link );
 193+ $interpResultView = new qp_InterpResultView( true );
 194+ $interpResultView->showInterpResults( $tags, $pollStore->interpResult, true );
 195+ return $tags;
204196 }
205197
206198 private function showUserVote( $pid, $uid ) {
@@ -226,13 +218,14 @@
227219 $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) );
228220 $output = wfMsg( 'qp_browse_to_user', $user_link ) . "<br />\n";
229221 $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n";
230 - $output .= $this->showAnswerHeader( $pollStore );
 222+ $headerTags = $this->getAnswerHeader( $pollStore );
 223+ $output .= qp_Renderer::renderTagArray( $headerTags );
 224+ unset( $headerTags );
231225 foreach ( $pollStore->Questions as &$qdata ) {
232226 if ( $pollStore->isUsedQuestion( $qdata->question_id ) ) {
233227 $output .= "<br />\n<b>" . $qdata->question_id . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n";
234 - $qview = $qdata->createView();
 228+ $qview = $qdata->getView();
235229 $output .= $qview->displayUserQuestionVote();
236 - unset( $qview );
237230 }
238231 }
239232 return $output;
@@ -252,17 +245,17 @@
253246 $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n";
254247 $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'stats_xls', 'id' => $pid ) ) . "<br />\n";
255248 $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_voices_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'voices_xls', 'id' => $pid ) ) . "<br />\n";
 249+ $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_interpretation_results_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'interpretation_xls', 'id' => $pid ) ) . "<br />\n";
256250 foreach ( $pollStore->Questions as &$qdata ) {
257 - $qview = $qdata->createView();
 251+ $qview = $qdata->getView();
258252 $output .= $qview->displayQuestionStats( $this, $pid );
259 - unset( $qview );
260253 }
261254 }
262255 }
263256 return $output;
264257 }
265258
266 - private function voicesToXLS( $pid ) {
 259+ private function writeXLS( $cmd, $pid ) {
267260 if ( $pid === null ) {
268261 return;
269262 }
@@ -273,93 +266,39 @@
274267 # use default IIS / Apache execution time limit which is much larger than default PHP limit
275268 set_time_limit( 300 );
276269 $poll_id = $pollStore->getPollId();
277 - $pollStore->loadQuestions();
278270 try {
279271 require_once( qp_Setup::$ExtDir . '/Excel/Excel_Writer.php' );
280272 $xls_fname = tempnam( "", ".xls" );
281273 $xls_workbook = new Spreadsheet_Excel_Writer_Workbook( $xls_fname );
282274 $xls_workbook->setVersion( 8 );
283 - $xls_worksheet = &$xls_workbook->addworksheet();
284 - $xls_worksheet->setInputEncoding( "utf-8" );
285 - $xls_worksheet->setPaper( 9 );
286 - $xls_rownum = 0;
287 - $format_heading = &$xls_workbook->addformat( array( 'bold' => 1 ) );
288 - $format_answer = &$xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) );
289 - $format_answer->setAlign( 'left' );
290 - $format_even = &$xls_workbook->addformat( array( 'fgcolor' => 0x2A, 'border' => 1 ) );
291 - $format_even->setAlign( 'left' );
292 - $format_odd = &$xls_workbook->addformat( array( 'fgcolor' => 0x23, 'border' => 1 ) );
293 - $format_odd->setAlign( 'left' );
294 - $first_question = true;
295 - foreach ( $pollStore->Questions as $qkey => &$qdata ) {
296 - if ( $first_question ) {
297 - $totalUsersAnsweredQuestion = $pollStore->totalUsersAnsweredQuestion( $qdata );
298 - $xls_worksheet->write( $xls_rownum, 0, $totalUsersAnsweredQuestion, $format_heading );
299 - $xls_worksheet->write( $xls_rownum++, 1, wfMsgExt( 'qp_users_answered_questions', array( 'parsemag' ), $totalUsersAnsweredQuestion ), $format_heading );
300 - $xls_rownum++;
301 - $first_question = false;
302 - }
303 - $xls_worksheet->write( $xls_rownum, 0, $qdata->question_id, $format_heading );
304 - $xls_worksheet->write( $xls_rownum++, 1, qp_Excel::prepareExcelString( $qdata->CommonQuestion ), $format_heading );
305 - if ( count( $qdata->CategorySpans ) > 0 ) {
306 - $row = array();
307 - foreach ( $qdata->CategorySpans as &$span ) {
308 - $row[] = qp_Excel::prepareExcelString( $span[ "name" ] );
309 - for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
310 - $row[] = "";
311 - }
312 - }
313 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
314 - }
315 - $row = array();
316 - foreach ( $qdata->Categories as &$categ ) {
317 - $row[] = qp_Excel::prepareExcelString( $categ[ "name" ] );
318 - }
319 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
320 -/*
321 - foreach ( $qdata->Percents as $pkey=>&$percent ) {
322 - $xls_worksheet->writerow( $xls_rownum + $pkey, 0, $percent );
323 - }
324 -*/
325 - $voters = array();
326 - $offset = 0;
327 - $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
328 - # iterate through the voters of the current poll (there might be many)
329 - while ( ( $limit = count( $voters = $pollStore->pollVotersPager( $offset ) ) ) > 0 ) {
330 - $uvoices = $pollStore->questionVoicesRange( $qdata->question_id, array_keys( $voters ) );
331 - # get each of proposal votes for current uid
332 - foreach ( $uvoices as $uid => &$pvoices ) {
333 - # output square table of proposal / category answers for each uid in uvoices array
334 - $voicesTable = array();
335 - foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
336 - $row = array_fill( 0, count( $qdata->Categories ), '' );
337 - if ( isset( $pvoices[$propkey] ) ) {
338 - foreach ( $pvoices[$propkey] as $catkey => $text_answer ) {
339 - $row[$catkey] = qp_Excel::prepareExcelString( $text_answer );
340 - }
341 - if ( $spansUsed ) {
342 - foreach ( $row as $catkey => &$cell ) {
343 - $cell = array( 0 => $cell );
344 - if ( $qdata->type == "multipleChoice" ) {
345 - $cell[ "format" ] = ( ( $catkey & 1 ) === 0 ) ? $format_even : $format_odd;
346 - } else {
347 - $cell[ "format" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format_even : $format_odd;
348 - }
349 - }
350 - }
351 - }
352 - $voicesTable[] = $row;
353 - }
354 - qp_Excel::writeFormattedTable( $xls_worksheet, $xls_rownum, 0, $voicesTable, $format_answer );
355 - $row = array();
356 - foreach ( $qdata->ProposalText as $ptext ) {
357 - $row[] = qp_Excel::prepareExcelString( $ptext );
358 - }
359 - $xls_worksheet->writecol( $xls_rownum, count( $qdata->Categories ), $row );
360 - $xls_rownum += count( $qdata->ProposalText ) + 1;
361 - }
362 - $offset += $limit;
363 - }
 275+ $qp_xls = qp_Excel::newFromWorksheet( $xls_workbook->addworksheet() );
 276+ # setup common formats
 277+ $format = array(
 278+ 'heading' => $xls_workbook->addformat( array( 'bold' => 1 ) ),
 279+ 'answer' => $xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) ),
 280+ 'even' => $xls_workbook->addformat( array( 'fgcolor' => 0x2A, 'border' => 1 ) ),
 281+ 'odd' => $xls_workbook->addformat( array( 'fgcolor' => 0x23, 'border' => 1 ) )
 282+ );
 283+ $format['answer']->setAlign( 'left' );
 284+ $format['even']->setAlign( 'left' );
 285+ $format['odd']->setAlign( 'left' );
 286+ switch ( $cmd ) {
 287+ case 'voices_xls' :
 288+ $qp_xls->voicesToXLS( $format, $pollStore );
 289+ break;
 290+ case 'stats_xls' :
 291+ # statistics export uses additional formats
 292+ $percent_num_format = '[Blue]0.0%;[Red]-0.0%;[Black]0%';
 293+ $format['percent'] = $xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) );
 294+ $format['percent']->setAlign( 'left' );
 295+ $format['percent']->setNumFormat( $percent_num_format );
 296+ $format['even']->setNumFormat( $percent_num_format );
 297+ $format['odd']->setNumFormat( $percent_num_format );
 298+ $qp_xls->statsToXLS( $format, $pollStore );
 299+ break;
 300+ case 'interpretation_xls' :
 301+ $qp_xls->interpretationToXLS( $format, $pollStore );
 302+ break;
364303 }
365304 $xls_workbook->close();
366305 header( 'Content-Type: application/x-msexcel; name="' . $poll_id . '.xls"' );
@@ -373,109 +312,6 @@
374313 }
375314 }
376315
377 - private function statsToXLS( $pid ) {
378 - if ( $pid === null ) {
379 - return;
380 - }
381 - $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
382 - if ( $pollStore->pid === null ) {
383 - return;
384 - }
385 - $poll_id = $pollStore->getPollId();
386 - $pollStore->loadQuestions();
387 - $pollStore->loadTotals();
388 - $pollStore->calculateStatistics();
389 - try {
390 - require_once( qp_Setup::$ExtDir . '/Excel/Excel_Writer.php' );
391 - $xls_fname = tempnam( "", ".xls" );
392 - $xls_workbook = new Spreadsheet_Excel_Writer_Workbook( $xls_fname );
393 - $xls_workbook->setVersion( 8 );
394 - $xls_worksheet = &$xls_workbook->addworksheet();
395 - $xls_worksheet->setInputEncoding( "utf-8" );
396 - $xls_worksheet->setPaper( 9 );
397 - $xls_rownum = 0;
398 - $percent_num_format = '[Blue]0.0%;[Red]-0.0%;[Black]0%';
399 - $format_heading = &$xls_workbook->addformat( array( 'bold' => 1 ) );
400 - $format_percent = &$xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) );
401 - $format_percent->setAlign( 'left' );
402 - $format_percent->setNumFormat( $percent_num_format );
403 - $format_even = &$xls_workbook->addformat( array( 'fgcolor' => 0x2A, 'border' => 1 ) );
404 - $format_even->setAlign( 'left' );
405 - $format_even->setNumFormat( $percent_num_format );
406 - $format_odd = &$xls_workbook->addformat( array( 'fgcolor' => 0x23, 'border' => 1 ) );
407 - $format_odd->setAlign( 'left' );
408 - $format_odd->setNumFormat( $percent_num_format );
409 - $first_question = true;
410 - foreach ( $pollStore->Questions as $qkey => &$qdata ) {
411 - if ( $first_question ) {
412 - $totalUsersAnsweredQuestion = $pollStore->totalUsersAnsweredQuestion( $qdata );
413 - $xls_worksheet->write( $xls_rownum, 0, $totalUsersAnsweredQuestion, $format_heading );
414 - $xls_worksheet->write( $xls_rownum++, 1, wfMsgExt( 'qp_users_answered_questions', array( 'parsemag' ), $totalUsersAnsweredQuestion ), $format_heading );
415 - $xls_rownum++;
416 - $first_question = false;
417 - }
418 - $xls_worksheet->write( $xls_rownum, 0, $qdata->question_id, $format_heading );
419 - $xls_worksheet->write( $xls_rownum++, 1, qp_Excel::prepareExcelString( $qdata->CommonQuestion ), $format_heading );
420 - if ( count( $qdata->CategorySpans ) > 0 ) {
421 - $row = array();
422 - foreach ( $qdata->CategorySpans as &$span ) {
423 - $row[] = qp_Excel::prepareExcelString( $span[ "name" ] );
424 - for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
425 - $row[] = "";
426 - }
427 - }
428 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
429 - }
430 - $row = array();
431 - foreach ( $qdata->Categories as &$categ ) {
432 - $row[] = qp_Excel::prepareExcelString( $categ[ "name" ] );
433 - }
434 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
435 -/*
436 - foreach ( $qdata->Percents as $pkey=>&$percent ) {
437 - $xls_worksheet->writerow( $xls_rownum + $pkey, 0, $percent );
438 - }
439 -*/
440 - $percentsTable = array();
441 - $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
442 - foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
443 - if ( isset( $qdata->Percents[ $propkey ] ) ) {
444 - $row = $qdata->Percents[ $propkey ];
445 - foreach ( $row as $catkey => &$cell ) {
446 - $cell = array( 0 => $cell );
447 - if ( $spansUsed ) {
448 - if ( $qdata->type == "multipleChoice" ) {
449 - $cell[ "format" ] = ( ( $catkey & 1 ) === 0 ) ? $format_even : $format_odd;
450 - } else {
451 - $cell[ "format" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format_even : $format_odd;
452 - }
453 - }
454 - }
455 - } else {
456 - $row = array_fill( 0, count( $qdata->Categories ), '' );
457 - }
458 - $percentsTable[] = $row;
459 - }
460 - qp_Excel::writeFormattedTable( $xls_worksheet, $xls_rownum, 0, $percentsTable, $format_percent );
461 - $row = array();
462 - foreach ( $qdata->ProposalText as $ptext ) {
463 - $row[] = qp_Excel::prepareExcelString( $ptext );
464 - }
465 - $xls_worksheet->writecol( $xls_rownum, count( $qdata->Categories ), $row );
466 - $xls_rownum += count( $qdata->ProposalText ) + 1;
467 - }
468 - $xls_workbook->close();
469 - header( 'Content-Type: application/x-msexcel; name="' . $poll_id . '.xls"' );
470 - header( 'Content-Disposition: inline; filename="' . $poll_id . '.xls"' );
471 - $fxls = @fopen( $xls_fname, "rb" );
472 - @fpassthru( $fxls );
473 - @unlink( $xls_fname );
474 - exit();
475 - } catch ( Exception $e ) {
476 - die( "Error while exporting poll statistics to Excel table\n" );
477 - }
478 - }
479 -
480316 static function getUsersLink() {
481317 return "<div>" . self::$UsersLink . "</div>\n";
482318 }
Index: trunk/extensions/QPoll/ctrl/qp_interpresult.php
@@ -8,6 +8,9 @@
99 * An interpretation result of user answer to the quiz
1010 */
1111 class qp_InterpResult {
 12+
 13+ private static $props = array( 'short', 'long', 'structured', 'error' );
 14+
1215 # short answer. it is supposed to be sortable and accountable in statistics
1316 # by default, it is private (displayed only in Special:Pollresults page)
1417 # blank value means short answer is unavailable
@@ -43,9 +46,8 @@
4447 * @param $init - optional array of properties to be initialized
4548 */
4649 function __construct( $init = null ) {
47 - $props = array( 'short', 'long', 'error' );
4850 if ( is_array( $init ) ) {
49 - foreach ( $props as $prop ) {
 51+ foreach ( self::$props as $prop ) {
5052 if ( array_key_exists( $prop, $init ) ) {
5153 $this->{ $prop } = $init[$prop];
5254 }
@@ -54,6 +56,15 @@
5557 }
5658 }
5759
 60+ function isEmpty() {
 61+ foreach ( self::$props as $prop ) {
 62+ if ( $this->{ $prop } !== '' ) {
 63+ return false;
 64+ }
 65+ }
 66+ return true;
 67+ }
 68+
5869 /**
5970 * "global" error message
6071 */
@@ -108,4 +119,54 @@
109120 return !$this->isError() || $this->storeErroneous;
110121 }
111122
 123+ /**
 124+ * Builds tabular representation of the current structured answer
 125+ * @return array containing nodes for every level of of structured answer,
 126+ * line per line;
 127+ *
 128+ * "line" is an array( 'keys'=>(,,),'vals'=>(,,) ) when
 129+ * current recursive node is an associative array;
 130+ * "line is an array( 'vals'=>value) when current recursive node
 131+ * is a scalar value.
 132+ *
 133+ */
 134+ function getStructuredAnswerTable() {
 135+ $strucTable = array();
 136+ $structured = unserialize( $this->structured );
 137+ $this->buildStructuredTable( $strucTable, $structured );
 138+ return $strucTable;
 139+ }
 140+
 141+ /**
 142+ * Build a projection of associative array tree to 2nd dimensional array
 143+ * @modifies $strucTable array destination 2nd dimensional array
 144+ * (see description in $this->getStructuredAnswerTable);
 145+ * @param $structured array current node of associative array tree
 146+ * @param $level_header string current "folder-like" prefix of
 147+ * structured answer nested key (levels are separated with " / ")
 148+ */
 149+ function buildStructuredTable( &$strucTable, &$structured, $level_header = '' ) {
 150+ $keys = array();
 151+ $vals = array();
 152+ if ( is_array( $structured ) ) {
 153+ foreach ( $structured as $key => &$val ) {
 154+ # display only non-numeric keys as "folders"
 155+ $level_key = is_int( $key ) ? '' : strval( $key );
 156+ # do not display '/' separator for root "folders"
 157+ $new_level_header = ( $level_header === '' ) ? $level_key : "{$level_header} / {$level_key}";
 158+ if ( is_array( $val ) ) {
 159+ $this->buildStructuredTable( $strucTable, $val, $new_level_header );
 160+ } else {
 161+ $keys[] = $new_level_header;
 162+ $vals[] = $val;
 163+ }
 164+ }
 165+ # associative keys and their vals
 166+ $strucTable[] = array( 'keys' => $keys, 'vals' => $vals );
 167+ } else {
 168+ # scalar value has no keys
 169+ $strucTable[] = array( 'vals' => strval( $structured ) );
 170+ }
 171+ }
 172+
112173 } /* end of qp_InterpResult class */
Index: trunk/extensions/QPoll/qp_user.php
@@ -197,6 +197,7 @@
198198 NS_QP_INTERPRETATION => 'Interpretation',
199199 NS_QP_INTERPRETATION_TALK => 'Interpretation_talk'
200200 );
 201+
201202 # stores interpretation script line numbers separately for
202203 # every script language (currently only php eval is implemented)
203204 # key is value of <qpinterpret> xml tag 'lang' attribute, value is source line counter
Index: trunk/extensions/QPoll/includes/qp_excel.php
@@ -38,14 +38,26 @@
3939 }
4040
4141 /**
42 - * PEAR Excel helper
 42+ * PEAR Excel helper / wrapper
4343 *
44 - * todo: PollResults::voicesToXLS() and PollResults::statsToXLS() should be refactored into
45 - * this class;
46 - *
4744 */
4845 class qp_Excel {
4946
 47+ # an instance of XLS worksheet
 48+ var $ws;
 49+ # list of formats added to workbook
 50+ var $format;
 51+ # current row number in a worksheet
 52+ var $rownum = 0;
 53+
 54+ static function newFromWorksheet( Spreadsheet_Excel_Writer_Worksheet $worksheet ) {
 55+ $self = new self();
 56+ $self->ws = $worksheet;
 57+ $self->ws->setInputEncoding( "utf-8" );
 58+ $self->ws->setPaper( 9 );
 59+ return $self;
 60+ }
 61+
5062 static function prepareExcelString( $s ) {
5163 if ( preg_match( '`^=.?`', $s ) ) {
5264 return "'" . $s;
@@ -53,20 +65,214 @@
5466 return $s;
5567 }
5668
57 - static function writeFormattedTable( $worksheet, $rownum, $colnum, &$table, $format = null ) {
 69+ function writeFormattedTable( $colnum, &$table, $format = null ) {
 70+ $ws = $this->ws;
5871 foreach ( $table as $rnum => &$row ) {
5972 foreach ( $row as $cnum => &$cell ) {
6073 if ( is_array( $cell ) ) {
6174 if ( array_key_exists( "format", $cell ) ) {
62 - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $cell[ "format" ] );
 75+ $ws->write( $this->rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $cell[ "format" ] );
6376 } else {
64 - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $format );
 77+ $ws->write( $this->rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $format );
6578 }
6679 } else {
67 - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell, $format );
 80+ $ws->write( $this->rownum + $rnum, $colnum + $cnum, $cell, $format );
6881 }
6982 }
7083 }
7184 }
7285
 86+ function writeHeader( $totalUsersAnsweredQuestion ) {
 87+ $this->ws->write( $this->rownum, 0, $totalUsersAnsweredQuestion, $this->format['heading'] );
 88+ $this->ws->write( $this->rownum++, 1, wfMsgExt( 'qp_users_answered_questions', array( 'parsemag' ), $totalUsersAnsweredQuestion ), $this->format['heading'] );
 89+ $this->rownum++;
 90+ }
 91+
 92+ function voicesToXls( $format, qp_PollStore $pollStore ) {
 93+ $this->format = &$format;
 94+ $pollStore->loadQuestions();
 95+ $ws = $this->ws;
 96+ $first_question = true;
 97+ foreach ( $pollStore->Questions as $qkey => &$qdata ) {
 98+ if ( $first_question ) {
 99+ $this->writeHeader( $pollStore->totalUsersAnsweredQuestion( $qdata ) );
 100+ } else {
 101+ # get maximum count of voters of the first question
 102+ $total_voters = $first_question_voters;
 103+ }
 104+ $ws->write( $this->rownum, 0, $qdata->question_id, $format['heading'] );
 105+ $ws->write( $this->rownum++, 1, self::prepareExcelString( $qdata->CommonQuestion ), $format['heading'] );
 106+ if ( count( $qdata->CategorySpans ) > 0 ) {
 107+ $row = array();
 108+ foreach ( $qdata->CategorySpans as &$span ) {
 109+ $row[] = self::prepareExcelString( $span[ "name" ] );
 110+ for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
 111+ $row[] = "";
 112+ }
 113+ }
 114+ $ws->writerow( $this->rownum++, 0, $row );
 115+ }
 116+ $row = array();
 117+ foreach ( $qdata->Categories as &$categ ) {
 118+ $row[] = self::prepareExcelString( $categ[ "name" ] );
 119+ }
 120+ $ws->writerow( $this->rownum++, 0, $row );
 121+/*
 122+ foreach ( $qdata->Percents as $pkey=>&$percent ) {
 123+ $ws->writerow( $this->rownum + $pkey, 0, $percent );
 124+ }
 125+*/
 126+ $voters = array();
 127+ $offset = 0;
 128+ $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
 129+ # iterate through the voters of the current poll (there might be many)
 130+ while ( ( $limit = count( $voters = $pollStore->pollVotersPager( $offset ) ) ) > 0 ) {
 131+ if ( !$first_question ) {
 132+ # do not export more user voices than first question has
 133+ for ( $total_voters -= $limit; $total_voters < 0 && $limit > 0; $total_voters++, $limit-- ) {
 134+ array_pop( $voters );
 135+ }
 136+ if ( count( $voters ) === 0 ) {
 137+ break;
 138+ }
 139+ }
 140+ $uvoices = $pollStore->questionVoicesRange( $qdata->question_id, array_keys( $voters ) );
 141+ # get each of proposal votes for current uid
 142+ foreach ( $uvoices as $uid => &$pvoices ) {
 143+ # output square table of proposal / category answers for each uid in uvoices array
 144+ $voicesTable = array();
 145+ foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
 146+ $row = array_fill( 0, count( $qdata->Categories ), '' );
 147+ if ( isset( $pvoices[$propkey] ) ) {
 148+ foreach ( $pvoices[$propkey] as $catkey => $text_answer ) {
 149+ $row[$catkey] = self::prepareExcelString( $text_answer );
 150+ }
 151+ if ( $spansUsed ) {
 152+ foreach ( $row as $catkey => &$cell ) {
 153+ $cell = array( 0 => $cell );
 154+ if ( $qdata->type == "multipleChoice" ) {
 155+ $cell['format'] = ( ( $catkey & 1 ) === 0 ) ? $format['even'] : $format['odd'];
 156+ } else {
 157+ $cell['format'] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format['even'] : $format['odd'];
 158+ }
 159+ }
 160+ }
 161+ }
 162+ $voicesTable[] = $row;
 163+ }
 164+ $this->writeFormattedTable( 0, $voicesTable, $format['answer'] );
 165+ $row = array();
 166+ foreach ( $qdata->ProposalText as $ptext ) {
 167+ $row[] = self::prepareExcelString( $ptext );
 168+ }
 169+ $ws->writecol( $this->rownum, count( $qdata->Categories ), $row );
 170+ $this->rownum += count( $qdata->ProposalText ) + 1;
 171+ }
 172+ if ( !$first_question && $total_voters < 1 ) {
 173+ # break on reaching the count of first question user voices
 174+ break;
 175+ }
 176+ $offset += $limit;
 177+ }
 178+ if ( $first_question ) {
 179+ # store maximum count of voters of the first question
 180+ $first_question_voters = $offset;
 181+ $first_question = false;
 182+ }
 183+ }
 184+ }
 185+
 186+ function statsToXls( $format, qp_PollStore $pollStore ) {
 187+ $this->format = &$format;
 188+ $pollStore->loadQuestions();
 189+ $pollStore->loadTotals();
 190+ $pollStore->calculateStatistics();
 191+ $ws = $this->ws;
 192+ $first_question = true;
 193+ foreach ( $pollStore->Questions as $qkey => &$qdata ) {
 194+ if ( $first_question ) {
 195+ $this->writeHeader( $pollStore->totalUsersAnsweredQuestion( $qdata ) );
 196+ $first_question = false;
 197+ }
 198+ $ws->write( $this->rownum, 0, $qdata->question_id, $format['heading'] );
 199+ $ws->write( $this->rownum++, 1, self::prepareExcelString( $qdata->CommonQuestion ), $format['heading'] );
 200+ if ( count( $qdata->CategorySpans ) > 0 ) {
 201+ $row = array();
 202+ foreach ( $qdata->CategorySpans as &$span ) {
 203+ $row[] = self::prepareExcelString( $span[ "name" ] );
 204+ for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
 205+ $row[] = "";
 206+ }
 207+ }
 208+ $ws->writerow( $this->rownum++, 0, $row );
 209+ }
 210+ $row = array();
 211+ foreach ( $qdata->Categories as &$categ ) {
 212+ $row[] = self::prepareExcelString( $categ[ "name" ] );
 213+ }
 214+ $ws->writerow( $this->rownum++, 0, $row );
 215+/*
 216+ foreach ( $qdata->Percents as $pkey=>&$percent ) {
 217+ $ws->writerow( $this->rownum + $pkey, 0, $percent );
 218+ }
 219+*/
 220+ $percentsTable = array();
 221+ $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
 222+ foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
 223+ if ( isset( $qdata->Percents[ $propkey ] ) ) {
 224+ $row = $qdata->Percents[ $propkey ];
 225+ foreach ( $row as $catkey => &$cell ) {
 226+ $cell = array( 0 => $cell );
 227+ if ( $spansUsed ) {
 228+ if ( $qdata->type == "multipleChoice" ) {
 229+ $cell['format'] = ( ( $catkey & 1 ) === 0 ) ? $format['even'] : $format['odd'];
 230+ } else {
 231+ $cell['format'] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format['even'] : $format['odd'];
 232+ }
 233+ }
 234+ }
 235+ } else {
 236+ $row = array_fill( 0, count( $qdata->Categories ), '' );
 237+ }
 238+ $percentsTable[] = $row;
 239+ }
 240+ $this->writeFormattedTable( 0, $percentsTable, $format['percent'] );
 241+ $row = array();
 242+ foreach ( $qdata->ProposalText as $ptext ) {
 243+ $row[] = self::prepareExcelString( $ptext );
 244+ }
 245+ $ws->writecol( $this->rownum, count( $qdata->Categories ), $row );
 246+ $this->rownum += count( $qdata->ProposalText ) + 1;
 247+ }
 248+ }
 249+
 250+ function interpretationToXLS( $format, qp_PollStore $pollStore ) {
 251+ $offset = 0;
 252+ $ws = $this->ws;
 253+ # iterate through the voters of the current poll (there might be many)
 254+ while ( ( $limit = count( $voters = $pollStore->pollVotersPager( $offset ) ) ) > 0 ) {
 255+ foreach ( $voters as &$voter ) {
 256+ if ( $voter['interpretation']->short != '' ) {
 257+ $ws->write( $this->rownum++, 0, self::prepareExcelString( wfMsg( 'qp_results_short_interpretation' ) ), $format['heading'] );
 258+ $ws->write( $this->rownum++, 0, self::prepareExcelString( $voter['interpretation']->short ) );
 259+ }
 260+ if ( $voter['interpretation']->structured != '' ) {
 261+ $ws->write( $this->rownum++, 0, self::prepareExcelString( wfMsg( 'qp_results_structured_interpretation' ) ), $format['heading'] );
 262+ $strucTable = $voter['interpretation']->getStructuredAnswerTable();
 263+ foreach ( $strucTable as &$line ) {
 264+ if ( isset( $line['keys'] ) ) {
 265+ # current node is associative array
 266+ $ws->writeRow( $this->rownum++, 0, $line['keys'], $format['odd'] );
 267+ $ws->writeRow( $this->rownum++, 0, $line['vals'] );
 268+ } else {
 269+ $ws->write( $this->rownum++, 0, $line['vals'] );
 270+ }
 271+ }
 272+ $this->rownum++;
 273+ }
 274+ }
 275+ $offset += $limit;
 276+ }
 277+ }
 278+
73279 } /* end of qp_Excel class */
Index: trunk/extensions/QPoll/view/qp_interpresultview.php
@@ -4,47 +4,105 @@
55 }
66
77 /**
8 - * View interpretation results of polls
 8+ * View interpretation results of polls; Used both in "ordinary" pages and
 9+ * in special pages;
 10+ * This cannot extend qp_AbstractView because there is currently no PPFrame
 11+ * in extension's special pages;
912 */
10 -class qp_InterpResultView extends qp_AbstractView {
 13+class qp_InterpResultView {
1114
12 - static function newFromBaseView( $baseView ) {
13 - return new self( $baseView->parser, $baseView->ppframe );
 15+ # pview is null in Special:Pollresults;
 16+ # valid view instance with PPFrame in "ordinary" pages
 17+ var $pview = null;
 18+ # a copy of qp_Setup::$show_interpretation or modified version
 19+ var $showInterpretation;
 20+
 21+ /**
 22+ * Creates new view without "parent" view associated - no PPFrame;
 23+ * @param $displayAll boolean true - display all results (for Special:Pollresults)
 24+ * false - display only allowed results (for end-user)
 25+ */
 26+ function __construct( $displayAll = false ) {
 27+ ## setup 'showInterpretation' property
 28+ # set pre-defined value
 29+ $this->showInterpretation = qp_Setup::$show_interpretation;
 30+ # make sure all required keys are available
 31+ foreach ( array( 'short', 'long', 'structured' ) as $val ) {
 32+ if ( !isset( $this->showInterpretation[$val] ) ) {
 33+ $this->showInterpretation[$val] = false;
 34+ }
 35+ }
 36+ if ( $displayAll ) {
 37+ # enable displaying all the kinds of intepretations
 38+ foreach ( $this->showInterpretation as &$val ) {
 39+ $val = true;
 40+ }
 41+ }
1442 }
1543
16 - function isCompatibleController( $ctrl ) {
17 - return $ctrl instanceof qp_InterpResult;
 44+ /**
 45+ * Instantiate with parent view (with PPFrame);
 46+ * Used for end-user display (long interpretation as wikitext)
 47+ */
 48+ static function newFromBaseView( qp_AbstractView $pview ) {
 49+ $self = new self( false );
 50+ $self->pview = $pview;
 51+ return $self;
1852 }
1953
2054 /**
2155 * Add interpretation results to tagarray of poll view
2256 */
23 - function showInterpResults( &$tagarray ) {
24 - $ctrl = $this->ctrl;
 57+ function showInterpResults( &$tagarray, qp_InterpResult $ctrl, $showDescriptions = false ) {
 58+ if ( $ctrl->isEmpty() ) {
 59+ return;
 60+ }
 61+ $interp = array();
 62+ if ( $showDescriptions ) {
 63+ $interp[] = array( '__tag' => 'div', wfMsg( 'qp_results_interpretation_header' ) );
 64+ }
 65+ # currently, error is not stored in DB, only the vote and long / short interpretations
 66+ # todo: is it worth to store it?
2567 if ( ( $scriptError = $ctrl->error ) != '' ) {
26 - $tagarray[] = array( '__tag' => 'div', 'class' => 'interp_error', qp_Setup::specialchars( $scriptError ) );
 68+ $interp[] = array( '__tag' => 'div', 'class' => 'interp_error', qp_Setup::specialchars( $scriptError ) );
2769 }
2870 # output long result, when permitted and available
29 - if ( qp_Setup::$show_interpretation['long'] &&
 71+ if ( $this->showInterpretation['long'] &&
3072 ( $answer = $ctrl->long ) !== '' ) {
31 - $tagarray[] = array( '__tag' => 'div', 'class' => 'interp_answer', qp_Setup::specialchars( $answer ) );
 73+ if ( $showDescriptions ) {
 74+ $interp[] = array( '__tag' => 'div', 'class' => 'interp_header', wfMsg( 'qp_results_long_interpretation' ) );
 75+ }
 76+ $interp[] = array( '__tag' => 'div', 'class' => 'interp_answer_body', is_null( $this->pview ) ? nl2br( qp_Setup::specialchars( $answer ) ) : $this->pview->rtp( $answer ) );
3277 }
3378 # output short result, when permitted and available
34 - if ( qp_Setup::$show_interpretation['short'] &&
 79+ if ( $this->showInterpretation['short'] &&
3580 ( $answer = $ctrl->short ) !== '' ) {
36 - $tagarray[] = array( '__tag' => 'div', 'class' => 'interp_answer', qp_Setup::specialchars( $answer ) );
 81+ if ( $showDescriptions ) {
 82+ $interp[] = array( '__tag' => 'div', 'class' => 'interp_header', wfMsg( 'qp_results_short_interpretation' ) );
 83+ }
 84+ $interp[] = array( '__tag' => 'div', 'class' => 'interp_answer_body', nl2br( qp_Setup::specialchars( $answer ) ) );
3785 }
38 - if ( qp_Setup::$show_interpretation['structured'] &&
 86+ if ( $this->showInterpretation['structured'] &&
3987 ( $answer = $ctrl->structured ) !== '' ) {
40 - $tagarray[] = array( '__tag' => 'div', 'class' => 'interp_answer', $this->renderStructuredAnswer() );
 88+ if ( $showDescriptions ) {
 89+ $interp[] = array( '__tag' => 'div', 'class' => 'interp_header', wfMsg( 'qp_results_structured_interpretation' ) );
 90+ }
 91+ $strucTable = $ctrl->getStructuredAnswerTable();
 92+ $rows = array();
 93+ foreach ( $strucTable as &$line ) {
 94+ if ( isset( $line['keys'] ) ) {
 95+ # current node is associative array
 96+ qp_Renderer::addRow( $rows, $line['keys'], '', 'th' );
 97+ qp_Renderer::addRow( $rows, $line['vals'] );
 98+ } else {
 99+ # current node is scalar value
 100+ qp_Renderer::addRow( $rows, array( $line['vals'] ) );
 101+ }
 102+ }
 103+ $interp[] = array( '__tag' => 'table', 'class' => 'structured_answer', $rows );
 104+ unset( $strucTable );
41105 }
 106+ $tagarray[] = array( '__tag' => 'div', 'class' => 'interp_answer', $interp );
42107 }
43108
44 - /**
45 - * todo: how can this be related to structured answer in XLS data export?
46 - */
47 - function renderStructuredAnswer() {
48 - return 'todo: implement';
49 - }
50 -
51109 } /* end of qp_InterpResultView class */
Index: trunk/extensions/QPoll/view/results/qp_questiondataresults.php
@@ -7,7 +7,7 @@
88 /**
99 * Render question data in Special:Pollresults
1010 *
11 - * *** Usually instantiated via $qdata->createView() ***
 11+ * *** Usually a singleton instantiated via $qdata->getView() ***
1212 *
1313 */
1414 class qp_QuestionDataResults {
@@ -15,6 +15,10 @@
1616 var $ctrl;
1717
1818 function __construct( qp_QuestionData $ctrl ) {
 19+ $this->setController( $ctrl );
 20+ }
 21+
 22+ function setController( qp_QuestionData $ctrl ) {
1923 $this->ctrl = $ctrl;
2024 }
2125
Index: trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php
@@ -7,7 +7,7 @@
88 /**
99 * Render question data in Special:Pollresults
1010 *
11 - * *** Usually instantiated via $qdata->createView() ***
 11+ * *** Usually a singleton instantiated via $qdata->getView() ***
1212 *
1313 */
1414 class qp_TextQuestionDataResults extends qp_QuestionDataResults {
Index: trunk/extensions/QPoll/view/poll/qp_pollview.php
@@ -99,8 +99,7 @@
100100 # output script-generated error, when available
101101 # render short/long/structured result, when permitted and available
102102 $interpResultView = qp_InterpResultView::newFromBaseView( $this );
103 - $interpResultView->setController( $pollStore->interpResult );
104 - $interpResultView->showInterpResults( $qpoll_div );
 103+ $interpResultView->showInterpResults( $qpoll_div, $pollStore->interpResult );
105104 # unused anymore
106105 unset( $interpResultView );
107106 # create voting form and fill it with messages and inputs

Status & tagging log