Index: trunk/extensions/QPoll/i18n/qp.i18n.php |
— | — | @@ -60,6 +60,7 @@ |
61 | 61 | 'qp_users_list' => 'List all users', |
62 | 62 | 'qp_browse_to_poll' => 'Browse to $1', |
63 | 63 | 'qp_browse_to_user' => 'Browse to $1', |
| 64 | + 'qp_browse_to_interpretation' => 'Browse to $1', |
64 | 65 | 'qp_votes_count' => '$1 {{PLURAL:$1|vote|votes}}', |
65 | 66 | 'qp_source_link' => 'Source', |
66 | 67 | 'qp_stats_link' => 'Statistics', |
— | — | @@ -79,12 +80,14 @@ |
80 | 81 | 'qp_results_line_qucl' => '$1: $2 $3', |
81 | 82 | 'qp_results_submit_attempts' => 'Submit attempts: $1', |
82 | 83 | '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', |
85 | 87 | 'qp_poll_has_no_interpretation' => 'This poll has no interpretation template defined in the poll\'s header.', |
86 | 88 | 'qp_interpetation_wrong_answer' => 'Wrong answer', |
87 | 89 | 'qp_export_to_xls' => 'Export statistics into XLS format', |
88 | 90 | 'qp_voices_to_xls' => 'Export voices into XLS format', |
| 91 | + 'qp_interpretation_results_to_xls' => 'Export answer interpretations into XLS format', |
89 | 92 | 'qp_users_answered_questions' => '$1 {{PLURAL:$1|user|users}} answered to the questions', |
90 | 93 | 'qp_func_no_such_poll' => 'No such poll ($1)', |
91 | 94 | 'qp_func_missing_question_id' => 'Please specify an existing question id (starting from 1) for the poll $1.', |
— | — | @@ -177,10 +180,6 @@ |
178 | 181 | * $6 is a link to the with link label {{msg-mw|qp_not_participated_link}}', |
179 | 182 | 'qp_results_submit_attempts' => 'Parameters: |
180 | 183 | * $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.', |
185 | 184 | 'qp_error_missed_dependance_poll' => 'Parameters: |
186 | 185 | * $1 is the poll ID of the poll having an error. |
187 | 186 | * $2 is a link to the page with the poll, that this erroneous poll depends on. |
— | — | @@ -2594,6 +2593,7 @@ |
2595 | 2594 | 'qp_users_list' => 'Список всех участников', |
2596 | 2595 | 'qp_browse_to_poll' => 'Перейти к $1', |
2597 | 2596 | 'qp_browse_to_user' => 'Перейти к $1', |
| 2597 | + 'qp_browse_to_interpretation' => 'Перейти к $1', |
2598 | 2598 | 'qp_votes_count' => '$1 {{PLURAL:$1|голос|голоса|голосов}}', |
2599 | 2599 | 'qp_source_link' => 'Исходный код', |
2600 | 2600 | 'qp_stats_link' => 'Статистика', |
— | — | @@ -2613,12 +2613,14 @@ |
2614 | 2614 | 'qp_results_line_qucl' => '$1: $2 $3', |
2615 | 2615 | 'qp_results_submit_attempts' => 'Попыток ответа: $1', |
2616 | 2616 | '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' => 'Структурная интерпретация', |
2619 | 2620 | 'qp_poll_has_no_interpretation' => 'Шаблон, используемый для интерпретации результатов опроса, не определен в заголовке опроса', |
2620 | 2621 | 'qp_interpetation_wrong_answer' => 'Неправильный ответ', |
2621 | 2622 | 'qp_export_to_xls' => 'Экспортировать статистику в XLS формате', |
2622 | 2623 | 'qp_voices_to_xls' => 'Экспортировать голоса в XLS формате', |
| 2624 | + 'qp_interpretation_results_to_xls' => 'Экспортировать интерпретации ответов в XLS формате', |
2623 | 2625 | 'qp_users_answered_questions' => 'На вопросы {{PLURAL:$1|ответил $1 участник|ответило $1 участника|ответили $1 участников}}', |
2624 | 2626 | 'qp_func_no_such_poll' => 'Опрос не найден ($1)', |
2625 | 2627 | 'qp_func_missing_question_id' => 'Укажите существующий идентификатор вопроса (начинающийся с единицы) для опроса $1', |
Index: trunk/extensions/QPoll/clientside/qp_user.css |
— | — | @@ -34,11 +34,14 @@ |
35 | 35 | /* Part for the inputfields */ |
36 | 36 | .qpoll a.input, .qpoll a.input:hover, .qpoll a.input:active, .qpoll a.input:visited { text-decoration:none; color:black; outline: 0 } |
37 | 37 | .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; } |
40 | 38 | .qpoll .cat_prefilled { color: blue; } |
41 | 39 | .qpoll .cat_noanswer { background-color: LightYellow; } |
42 | 40 | .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; } |
43 | 46 | |
44 | 47 | /* script view */ |
45 | 48 | .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 @@ |
10 | 10 | .qpoll table.qdata tr.qdatatext td { text-align: left; } |
11 | 11 | .qpoll .cat_part { background-color: Lightyellow; border: 1px solid gray; padding: 0 0.5em 0 0.5em; } |
12 | 12 | .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; } |
14 | 15 | .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 @@ |
15 | 15 | */ |
16 | 16 | class qp_QuestionData { |
17 | 17 | |
| 18 | + # associated view instance (singleton) |
| 19 | + static protected $view; |
| 20 | + |
18 | 21 | // DB index (with current scheme is non-unique) |
19 | 22 | var $question_id = null; |
20 | 23 | // common properties |
— | — | @@ -38,6 +41,7 @@ |
39 | 42 | * another entries of $argv define property names and their values |
40 | 43 | */ |
41 | 44 | function __construct( $argv ) { |
| 45 | + self::$view = new stdClass; |
42 | 46 | if ( array_key_exists( 'from', $argv ) ) { |
43 | 47 | switch ( $argv[ 'from' ] ) { |
44 | 48 | case 'postdata' : |
— | — | @@ -66,10 +70,15 @@ |
67 | 71 | } |
68 | 72 | |
69 | 73 | /** |
70 | | - * Create appropriate view for Special:Pollresults |
| 74 | + * Get appropriate view for Special:Pollresults |
71 | 75 | */ |
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; |
74 | 83 | } |
75 | 84 | |
76 | 85 | /** |
— | — | @@ -177,8 +186,16 @@ |
178 | 187 | /** |
179 | 188 | * Questions of type="text" require a different view logic in Special:Pollresults page |
180 | 189 | */ |
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; |
183 | 200 | } |
184 | 201 | |
185 | 202 | } /* end of qp_TextQuestionData class */ |
Index: trunk/extensions/QPoll/model/qp_pollstore.php |
— | — | @@ -95,6 +95,9 @@ |
96 | 96 | $title = $wgParser->getTitle(); |
97 | 97 | } |
98 | 98 | $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 | + } |
99 | 102 | $this->mPollId = $argv[ 'poll_id' ]; |
100 | 103 | if ( array_key_exists( 'order_id', $argv ) ) { |
101 | 104 | $this->mOrderId = $argv[ 'order_id' ]; |
— | — | @@ -125,8 +128,10 @@ |
126 | 129 | $this->setPid(); |
127 | 130 | } else { |
128 | 131 | $this->loadPid(); |
129 | | - if ( is_null( $this->pid ) ) { |
| 132 | + if ( is_null( $this->pid ) && |
| 133 | + $this->pollDescIsValid() ) { |
130 | 134 | # try to create poll description (DB state was incomplete) |
| 135 | + # todo: check, whether this is really required for random questions |
131 | 136 | $this->setPid(); |
132 | 137 | } |
133 | 138 | } |
— | — | @@ -153,6 +158,8 @@ |
154 | 159 | $this->randomQuestionCount = $row->random_question_count; |
155 | 160 | } |
156 | 161 | break; |
| 162 | + default : |
| 163 | + throw new MWException( 'Unknown value of "from" parameter: ' . $argv[ 'from' ] . ' in ' . __METHOD__ ); |
157 | 164 | } |
158 | 165 | } |
159 | 166 | } |
— | — | @@ -204,6 +211,26 @@ |
205 | 212 | return $this->mPollId; |
206 | 213 | } |
207 | 214 | |
| 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 | + |
208 | 235 | # returns Title object, to get a URI path, use Title::getFullText()/getPrefixedText() on it |
209 | 236 | function getTitle() { |
210 | 237 | if ( $this->mArticleId === 0 ) { |
— | — | @@ -286,7 +313,9 @@ |
287 | 314 | |
288 | 315 | /** |
289 | 316 | * 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; |
291 | 320 | */ |
292 | 321 | function pollVotersPager( $offset = 0, $limit = 20 ) { |
293 | 322 | if ( $this->pid === null ) { |
— | — | @@ -294,7 +323,7 @@ |
295 | 324 | } |
296 | 325 | $qp_users_polls = self::$db->tableName( 'qp_users_polls' ); |
297 | 326 | $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 " . |
299 | 328 | "FROM $qp_users_polls qup " . |
300 | 329 | "INNER JOIN $qp_users qu ON qup.uid = qu.uid " . |
301 | 330 | "WHERE pid = " . intval( $this->pid ) . " " . |
— | — | @@ -302,7 +331,14 @@ |
303 | 332 | $res = self::$db->query( $query, __METHOD__ ); |
304 | 333 | $result = array(); |
305 | 334 | 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 | + ); |
307 | 343 | } |
308 | 344 | return $result; |
309 | 345 | } |
Index: trunk/extensions/QPoll/specials/qp_results.php |
— | — | @@ -121,15 +121,11 @@ |
122 | 122 | } |
123 | 123 | break; |
124 | 124 | case 'stats_xls': |
125 | | - if ( $pid !== null ) { |
126 | | - $pid = intval( $pid ); |
127 | | - $this->statsToXLS( $pid ); |
128 | | - } |
129 | | - break; |
130 | 125 | case 'voices_xls': |
| 126 | + case 'interpretation_xls': |
131 | 127 | if ( $pid !== null ) { |
132 | 128 | $pid = intval( $pid ); |
133 | | - $this->voicesToXLS( $pid ); |
| 129 | + $this->writeXLS( $cmd, $pid ); |
134 | 130 | } |
135 | 131 | break; |
136 | 132 | case 'uvote': |
— | — | @@ -181,25 +177,21 @@ |
182 | 178 | $wgOut->addHTML( $output . '</div>' ); |
183 | 179 | } |
184 | 180 | |
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 | + ); |
187 | 185 | $interpTitle = $pollStore->getInterpTitle(); |
188 | 186 | 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; |
191 | 189 | } |
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; |
204 | 196 | } |
205 | 197 | |
206 | 198 | private function showUserVote( $pid, $uid ) { |
— | — | @@ -226,13 +218,14 @@ |
227 | 219 | $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) ); |
228 | 220 | $output = wfMsg( 'qp_browse_to_user', $user_link ) . "<br />\n"; |
229 | 221 | $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 ); |
231 | 225 | foreach ( $pollStore->Questions as &$qdata ) { |
232 | 226 | if ( $pollStore->isUsedQuestion( $qdata->question_id ) ) { |
233 | 227 | $output .= "<br />\n<b>" . $qdata->question_id . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n"; |
234 | | - $qview = $qdata->createView(); |
| 228 | + $qview = $qdata->getView(); |
235 | 229 | $output .= $qview->displayUserQuestionVote(); |
236 | | - unset( $qview ); |
237 | 230 | } |
238 | 231 | } |
239 | 232 | return $output; |
— | — | @@ -252,17 +245,17 @@ |
253 | 246 | $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n"; |
254 | 247 | $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'stats_xls', 'id' => $pid ) ) . "<br />\n"; |
255 | 248 | $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"; |
256 | 250 | foreach ( $pollStore->Questions as &$qdata ) { |
257 | | - $qview = $qdata->createView(); |
| 251 | + $qview = $qdata->getView(); |
258 | 252 | $output .= $qview->displayQuestionStats( $this, $pid ); |
259 | | - unset( $qview ); |
260 | 253 | } |
261 | 254 | } |
262 | 255 | } |
263 | 256 | return $output; |
264 | 257 | } |
265 | 258 | |
266 | | - private function voicesToXLS( $pid ) { |
| 259 | + private function writeXLS( $cmd, $pid ) { |
267 | 260 | if ( $pid === null ) { |
268 | 261 | return; |
269 | 262 | } |
— | — | @@ -273,93 +266,39 @@ |
274 | 267 | # use default IIS / Apache execution time limit which is much larger than default PHP limit |
275 | 268 | set_time_limit( 300 ); |
276 | 269 | $poll_id = $pollStore->getPollId(); |
277 | | - $pollStore->loadQuestions(); |
278 | 270 | try { |
279 | 271 | require_once( qp_Setup::$ExtDir . '/Excel/Excel_Writer.php' ); |
280 | 272 | $xls_fname = tempnam( "", ".xls" ); |
281 | 273 | $xls_workbook = new Spreadsheet_Excel_Writer_Workbook( $xls_fname ); |
282 | 274 | $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; |
364 | 303 | } |
365 | 304 | $xls_workbook->close(); |
366 | 305 | header( 'Content-Type: application/x-msexcel; name="' . $poll_id . '.xls"' ); |
— | — | @@ -373,109 +312,6 @@ |
374 | 313 | } |
375 | 314 | } |
376 | 315 | |
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 | | - |
480 | 316 | static function getUsersLink() { |
481 | 317 | return "<div>" . self::$UsersLink . "</div>\n"; |
482 | 318 | } |
Index: trunk/extensions/QPoll/ctrl/qp_interpresult.php |
— | — | @@ -8,6 +8,9 @@ |
9 | 9 | * An interpretation result of user answer to the quiz |
10 | 10 | */ |
11 | 11 | class qp_InterpResult { |
| 12 | + |
| 13 | + private static $props = array( 'short', 'long', 'structured', 'error' ); |
| 14 | + |
12 | 15 | # short answer. it is supposed to be sortable and accountable in statistics |
13 | 16 | # by default, it is private (displayed only in Special:Pollresults page) |
14 | 17 | # blank value means short answer is unavailable |
— | — | @@ -43,9 +46,8 @@ |
44 | 47 | * @param $init - optional array of properties to be initialized |
45 | 48 | */ |
46 | 49 | function __construct( $init = null ) { |
47 | | - $props = array( 'short', 'long', 'error' ); |
48 | 50 | if ( is_array( $init ) ) { |
49 | | - foreach ( $props as $prop ) { |
| 51 | + foreach ( self::$props as $prop ) { |
50 | 52 | if ( array_key_exists( $prop, $init ) ) { |
51 | 53 | $this->{ $prop } = $init[$prop]; |
52 | 54 | } |
— | — | @@ -54,6 +56,15 @@ |
55 | 57 | } |
56 | 58 | } |
57 | 59 | |
| 60 | + function isEmpty() { |
| 61 | + foreach ( self::$props as $prop ) { |
| 62 | + if ( $this->{ $prop } !== '' ) { |
| 63 | + return false; |
| 64 | + } |
| 65 | + } |
| 66 | + return true; |
| 67 | + } |
| 68 | + |
58 | 69 | /** |
59 | 70 | * "global" error message |
60 | 71 | */ |
— | — | @@ -108,4 +119,54 @@ |
109 | 120 | return !$this->isError() || $this->storeErroneous; |
110 | 121 | } |
111 | 122 | |
| 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 | + |
112 | 173 | } /* end of qp_InterpResult class */ |
Index: trunk/extensions/QPoll/qp_user.php |
— | — | @@ -197,6 +197,7 @@ |
198 | 198 | NS_QP_INTERPRETATION => 'Interpretation', |
199 | 199 | NS_QP_INTERPRETATION_TALK => 'Interpretation_talk' |
200 | 200 | ); |
| 201 | + |
201 | 202 | # stores interpretation script line numbers separately for |
202 | 203 | # every script language (currently only php eval is implemented) |
203 | 204 | # 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 @@ |
39 | 39 | } |
40 | 40 | |
41 | 41 | /** |
42 | | - * PEAR Excel helper |
| 42 | + * PEAR Excel helper / wrapper |
43 | 43 | * |
44 | | - * todo: PollResults::voicesToXLS() and PollResults::statsToXLS() should be refactored into |
45 | | - * this class; |
46 | | - * |
47 | 44 | */ |
48 | 45 | class qp_Excel { |
49 | 46 | |
| 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 | + |
50 | 62 | static function prepareExcelString( $s ) { |
51 | 63 | if ( preg_match( '`^=.?`', $s ) ) { |
52 | 64 | return "'" . $s; |
— | — | @@ -53,20 +65,214 @@ |
54 | 66 | return $s; |
55 | 67 | } |
56 | 68 | |
57 | | - static function writeFormattedTable( $worksheet, $rownum, $colnum, &$table, $format = null ) { |
| 69 | + function writeFormattedTable( $colnum, &$table, $format = null ) { |
| 70 | + $ws = $this->ws; |
58 | 71 | foreach ( $table as $rnum => &$row ) { |
59 | 72 | foreach ( $row as $cnum => &$cell ) { |
60 | 73 | if ( is_array( $cell ) ) { |
61 | 74 | 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" ] ); |
63 | 76 | } else { |
64 | | - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $format ); |
| 77 | + $ws->write( $this->rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $format ); |
65 | 78 | } |
66 | 79 | } else { |
67 | | - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell, $format ); |
| 80 | + $ws->write( $this->rownum + $rnum, $colnum + $cnum, $cell, $format ); |
68 | 81 | } |
69 | 82 | } |
70 | 83 | } |
71 | 84 | } |
72 | 85 | |
| 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 | + |
73 | 279 | } /* end of qp_Excel class */ |
Index: trunk/extensions/QPoll/view/qp_interpresultview.php |
— | — | @@ -4,47 +4,105 @@ |
5 | 5 | } |
6 | 6 | |
7 | 7 | /** |
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; |
9 | 12 | */ |
10 | | -class qp_InterpResultView extends qp_AbstractView { |
| 13 | +class qp_InterpResultView { |
11 | 14 | |
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 | + } |
14 | 42 | } |
15 | 43 | |
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; |
18 | 52 | } |
19 | 53 | |
20 | 54 | /** |
21 | 55 | * Add interpretation results to tagarray of poll view |
22 | 56 | */ |
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? |
25 | 67 | 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 ) ); |
27 | 69 | } |
28 | 70 | # output long result, when permitted and available |
29 | | - if ( qp_Setup::$show_interpretation['long'] && |
| 71 | + if ( $this->showInterpretation['long'] && |
30 | 72 | ( $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 ) ); |
32 | 77 | } |
33 | 78 | # output short result, when permitted and available |
34 | | - if ( qp_Setup::$show_interpretation['short'] && |
| 79 | + if ( $this->showInterpretation['short'] && |
35 | 80 | ( $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 ) ) ); |
37 | 85 | } |
38 | | - if ( qp_Setup::$show_interpretation['structured'] && |
| 86 | + if ( $this->showInterpretation['structured'] && |
39 | 87 | ( $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 ); |
41 | 105 | } |
| 106 | + $tagarray[] = array( '__tag' => 'div', 'class' => 'interp_answer', $interp ); |
42 | 107 | } |
43 | 108 | |
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 | | - |
51 | 109 | } /* end of qp_InterpResultView class */ |
Index: trunk/extensions/QPoll/view/results/qp_questiondataresults.php |
— | — | @@ -7,7 +7,7 @@ |
8 | 8 | /** |
9 | 9 | * Render question data in Special:Pollresults |
10 | 10 | * |
11 | | - * *** Usually instantiated via $qdata->createView() *** |
| 11 | + * *** Usually a singleton instantiated via $qdata->getView() *** |
12 | 12 | * |
13 | 13 | */ |
14 | 14 | class qp_QuestionDataResults { |
— | — | @@ -15,6 +15,10 @@ |
16 | 16 | var $ctrl; |
17 | 17 | |
18 | 18 | function __construct( qp_QuestionData $ctrl ) { |
| 19 | + $this->setController( $ctrl ); |
| 20 | + } |
| 21 | + |
| 22 | + function setController( qp_QuestionData $ctrl ) { |
19 | 23 | $this->ctrl = $ctrl; |
20 | 24 | } |
21 | 25 | |
Index: trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php |
— | — | @@ -7,7 +7,7 @@ |
8 | 8 | /** |
9 | 9 | * Render question data in Special:Pollresults |
10 | 10 | * |
11 | | - * *** Usually instantiated via $qdata->createView() *** |
| 11 | + * *** Usually a singleton instantiated via $qdata->getView() *** |
12 | 12 | * |
13 | 13 | */ |
14 | 14 | class qp_TextQuestionDataResults extends qp_QuestionDataResults { |
Index: trunk/extensions/QPoll/view/poll/qp_pollview.php |
— | — | @@ -99,8 +99,7 @@ |
100 | 100 | # output script-generated error, when available |
101 | 101 | # render short/long/structured result, when permitted and available |
102 | 102 | $interpResultView = qp_InterpResultView::newFromBaseView( $this ); |
103 | | - $interpResultView->setController( $pollStore->interpResult ); |
104 | | - $interpResultView->showInterpResults( $qpoll_div ); |
| 103 | + $interpResultView->showInterpResults( $qpoll_div, $pollStore->interpResult ); |
105 | 104 | # unused anymore |
106 | 105 | unset( $interpResultView ); |
107 | 106 | # create voting form and fill it with messages and inputs |