Index: trunk/extensions/QPoll/i18n/qp.i18n.php |
— | — | @@ -116,6 +116,7 @@ |
117 | 117 | 'qp_error_non_unique_choice' => 'This question requires unique proposal answer.', |
118 | 118 | 'qp_error_category_name_empty' => 'Category name is empty.', |
119 | 119 | 'qp_error_proposal_text_empty' => 'Proposal text is empty.', |
| 120 | + 'qp_error_too_long_proposal_text' => 'Proposal text is too long to be stored in the database', |
120 | 121 | 'qp_error_too_few_categories' => 'At least two categories must be defined.', |
121 | 122 | 'qp_error_too_few_spans' => 'Every category group must contain at least two subcategories.', |
122 | 123 | 'qp_error_no_answer' => 'Unanswered proposal.', |
— | — | @@ -2615,6 +2616,7 @@ |
2616 | 2617 | 'qp_error_non_unique_choice' => 'Данный вопрос требует чтобы выбранный вариант ответа не использовался ранее', |
2617 | 2618 | 'qp_error_category_name_empty' => 'Отсутствует название варианта ответа', |
2618 | 2619 | 'qp_error_proposal_text_empty' => 'Отсутствует текст строки вопроса', |
| 2620 | + 'qp_error_too_long_proposal_text' => 'Строка вопроса слишком длинна для сохранения в базе данных', |
2619 | 2621 | 'qp_error_too_few_categories' => 'Каждый вопрос должен иметь по крайней мере два варианта ответа', |
2620 | 2622 | 'qp_error_too_few_spans' => 'Каждая подкатегория вопроса требует по меньшей мере два варианта ответа', |
2621 | 2623 | 'qp_error_no_answer' => 'Нет ответа на вопрос', |
Index: trunk/extensions/QPoll/clientside/qp_user.css |
— | — | @@ -9,7 +9,8 @@ |
10 | 10 | .qpoll table.object .spans { color:gray; } |
11 | 11 | .qpoll table.object .categories { color:#444455; } |
12 | 12 | .qpoll table.object .proposal { } |
13 | | -.qpoll .proposalerror { color:red; font-weight:600; } |
| 13 | +.qpoll span.proposalerror { color:red; font-weight:600; } |
| 14 | +.qpoll tr.proposalerror { background-color: Snow; } |
14 | 15 | .qpoll .settings td { padding: 0.1em 0.4em 0.1em 0.4em } |
15 | 16 | .qpoll table.settings { background-color:transparent; } |
16 | 17 | /* Part for the basic types's inputs. */ |
— | — | @@ -34,9 +35,10 @@ |
35 | 36 | .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 */ |
36 | 37 | .qpoll .interp_error { border: 2px solid gray; padding: 0.5em; color: white; background-color: red; } |
37 | 38 | .qpoll .interp_answer { border: 2px solid gray; padding: 0.5em; color: black; background-color: lightblue; } |
| 39 | +.qpoll .cat_prefilled { color: blue; } |
| 40 | +.qpoll .cat_noanswer { background-color: LightYellow; } |
| 41 | +.qpoll .cell_error { background-color: #D700D7; } |
38 | 42 | |
39 | | -.qpoll .cell_errror { background-color: #D700D7; } |
40 | | - |
41 | 43 | /* script view */ |
42 | 44 | .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; } |
43 | 45 | .qpoll .script_view { font-family: monospace, "Courier New"; white-space:pre; overflow:auto; color:black; background-color: lightgray; border-right: 1px solid darkgray; border-top: 1px solid darkgray; border-bottom: 1px solid darkgray; padding: 0.5em; } |
Index: trunk/extensions/QPoll/clientside/qp_results.css |
— | — | @@ -6,5 +6,7 @@ |
7 | 7 | .qpoll table.pollresults td.stats {background-color: Azure;} |
8 | 8 | .qpoll table.pollresults td.spaneven {background-color: Aquamarine;} |
9 | 9 | .qpoll table.pollresults td.spanodd {background-color: Moccasin;} |
| 10 | +.qpoll .cat_part { background-color: Lightyellow; border: 1px solid gray; padding: 0 0.5em 0 0.5em; } |
| 11 | +.qpoll .cat_unknown { color: white; background-color: IndianRed; border: 1px solid gray; padding: 0 0.5em 0 0.5em; } |
10 | 12 | .qpoll .interp_answer { border: 1px solid gray; padding: 0; margin: 0; color: black; background-color: lightgray; font-weight:bold; line-height:2em; } |
11 | 13 | .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; } |
Index: trunk/extensions/QPoll/clientside/qp_user.js |
— | — | @@ -34,14 +34,15 @@ |
35 | 35 | |
36 | 36 | (function() { |
37 | 37 | function uniqueRadioButtonColumn() { |
38 | | -// uq1c2p2 |
| 38 | + // example of input id: 'uq1c2p2' |
39 | 39 | var propId, inp; |
40 | 40 | var id = new String( this.getAttribute('id') ); |
41 | | -// IE split is really buggy, so we have to search for p letter instead, was: |
42 | | -// var params = id.split('/(uq\d{1,}?c\d{1,}?p)(\d{1,}?)/g'); |
43 | | -// if ( params !== null && params[1] !== null && params[2] != null ) { |
44 | | -// var currPropId = parseInt( params[2] ); |
45 | | -// var basepart = params[1]; |
| 41 | + // IE split is really buggy, so we have to search for p letter instead, was: |
| 42 | + // var params = id.split('/(uq\d{1,}?c\d{1,}?p)(\d{1,}?)/g'); |
| 43 | + // if ( params !== null && params[1] !== null && params[2] != null ) { |
| 44 | + // var currPropId = parseInt( params[2] ); |
| 45 | + // var basepart = params[1]; |
| 46 | + // ... |
46 | 47 | var p = id.indexOf( 'p' ) + 1; |
47 | 48 | var currPropId = parseInt( id.substring( p ) ); |
48 | 49 | var basepart = id.substring( 0, p ); |
— | — | @@ -55,7 +56,7 @@ |
56 | 57 | } |
57 | 58 | |
58 | 59 | function clickMixedRow() { |
59 | | -// mx1p2c2 |
| 60 | + // example of input id: 'mx1p2c2' |
60 | 61 | var catId, inp; |
61 | 62 | var id = new String( this.getAttribute('id') ); |
62 | 63 | var c = id.indexOf( 'c' ) + 1; |
— | — | @@ -79,33 +80,54 @@ |
80 | 81 | } |
81 | 82 | } |
82 | 83 | } |
| 84 | + |
| 85 | + function clickPrefilledText() { |
| 86 | + if ( this.className === 'cat_prefilled' ) { |
| 87 | + this.select(); |
| 88 | + this.className = 'cat_part'; |
| 89 | + } |
| 90 | + } |
| 91 | + |
83 | 92 | /** |
84 | 93 | * Prepare the Poll for "javascriptable" browsers |
85 | 94 | */ |
86 | 95 | function preparePoll() { |
87 | | - var bodyContentDiv = document.getElementById('bodyContent').getElementsByTagName('div'); |
| 96 | + var bodyContentDiv = document.getElementById( 'bodyContent' ).getElementsByTagName( 'div' ); |
88 | 97 | var inputFuncId; |
89 | | - for(var i=0; i<bodyContentDiv.length; ++i) { |
90 | | - if(bodyContentDiv[i].className == 'qpoll') { |
91 | | - var input = bodyContentDiv[i].getElementsByTagName('input'); |
92 | | - for(var j=0; j<input.length; ++j) { |
| 98 | + for ( var i=0; i<bodyContentDiv.length; ++i ) { |
| 99 | + if ( bodyContentDiv[i].className == 'qpoll' ) { |
| 100 | + var input = bodyContentDiv[i].getElementsByTagName( 'input' ); |
| 101 | + for ( var j=0; j<input.length; ++j ) { |
93 | 102 | if ( input[j].id ) { |
| 103 | + // only unique or mixed questions currently have id |
94 | 104 | inputFuncId = new String( input[j].id ).slice( 0, 2 ); |
95 | 105 | switch ( inputFuncId ) { |
96 | | - case "uq": |
97 | | - if ( input[j].type == "radio" ) { |
98 | | - // unset the column of radiobuttons in case of unique proposal |
99 | | - addEvent( input[j], "click", uniqueRadioButtonColumn ); |
100 | | - } |
| 106 | + case "uq": |
| 107 | + if ( input[j].type == "radio" ) { |
| 108 | + // unset the column of radiobuttons in case of unique proposal |
| 109 | + addEvent( input[j], "click", uniqueRadioButtonColumn ); |
| 110 | + } |
101 | 111 | break; |
102 | | - case "mx": |
103 | | - // unset the row of checkboxes in case of "mixed" question type |
104 | | - addEvent( input[j], "click", clickMixedRow ); |
| 112 | + case "mx": |
| 113 | + // unset the row of checkboxes in case of "mixed" question type |
| 114 | + addEvent( input[j], "click", clickMixedRow ); |
105 | 115 | break; |
106 | 116 | } |
107 | 117 | } else { |
108 | | - // Add the possibility of unchecking radio buttons |
109 | | - addEvent(input[j],"dblclick", function() { this.checked = false; }); |
| 118 | + // check non-unique, non-mixed tabular questions and |
| 119 | + // text questions |
| 120 | + switch ( input[j].getAttribute( 'type' ) ) { |
| 121 | + case 'radio' : |
| 122 | + // Add the possibility of unchecking radio buttons |
| 123 | + addEvent( input[j], "dblclick", function() { this.checked = false; } ); |
| 124 | + break; |
| 125 | + case 'text' : |
| 126 | + if ( input[j].className === 'cat_prefilled' ) { |
| 127 | + // Add the handler to clear prefilled text |
| 128 | + addEvent( input[j], "click", clickPrefilledText ); |
| 129 | + } |
| 130 | + break; |
| 131 | + } |
110 | 132 | } |
111 | 133 | } |
112 | 134 | } |
Index: trunk/extensions/QPoll/qp_questiondata.php |
— | — | @@ -0,0 +1,273 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 5 | + die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
| 6 | +} |
| 7 | + |
| 8 | +/** |
| 9 | + * Poll's single question data object RAM storage |
| 10 | + * ( instances usually have short name qdata ) |
| 11 | + * |
| 12 | + * Since v0.8.0 it is also used as "mini-view" for Special:Pollresults page |
| 13 | + * because different types of questions should be displayed differently |
| 14 | + * |
| 15 | + */ |
| 16 | +class qp_QuestionData { |
| 17 | + |
| 18 | + // DB index (with current scheme is non-unique) |
| 19 | + var $question_id = null; |
| 20 | + // common properties |
| 21 | + var $type; |
| 22 | + var $CommonQuestion; |
| 23 | + var $Categories; |
| 24 | + var $CategorySpans; |
| 25 | + var $ProposalText; |
| 26 | + var $ProposalCategoryId; |
| 27 | + var $ProposalCategoryText; |
| 28 | + var $alreadyVoted = false; // whether the selected user already voted this question ? |
| 29 | + // statistics storage |
| 30 | + var $Votes = null; |
| 31 | + var $Percents = null; |
| 32 | + |
| 33 | + /** |
| 34 | + * Please do not instantiate directly, use qp_PollStore::newQuestionData() instead |
| 35 | + */ |
| 36 | + function __construct( $argv ) { |
| 37 | + if ( array_key_exists( 'from', $argv ) ) { |
| 38 | + switch ( $argv[ 'from' ] ) { |
| 39 | + case 'postdata' : |
| 40 | + $this->type = $argv[ 'type' ]; |
| 41 | + $this->CommonQuestion = $argv[ 'common_question' ]; |
| 42 | + $this->Categories = $argv[ 'categories' ]; |
| 43 | + $this->CategorySpans = $argv[ 'category_spans' ]; |
| 44 | + $this->ProposalText = $argv[ 'proposal_text' ]; |
| 45 | + $this->ProposalCategoryId = $argv[ 'proposal_category_id' ]; |
| 46 | + $this->ProposalCategoryText = $argv[ 'proposal_category_text' ]; |
| 47 | + break; |
| 48 | + case 'qid' : |
| 49 | + $this->question_id = $argv[ 'qid' ]; |
| 50 | + $this->type = $argv[ 'type' ]; |
| 51 | + $this->CommonQuestion = $argv[ 'common_question' ]; |
| 52 | + $this->Categories = Array(); |
| 53 | + $this->CategorySpans = Array(); |
| 54 | + $this->ProposalText = Array(); |
| 55 | + $this->ProposalCategoryId = Array(); |
| 56 | + $this->ProposalCategoryText = Array(); |
| 57 | + break; |
| 58 | + } |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + // integrate spans into categories |
| 63 | + function packSpans() { |
| 64 | + if ( count( $this->CategorySpans ) > 0 ) { |
| 65 | + foreach ( $this->Categories as &$Cat ) { |
| 66 | + if ( array_key_exists( 'spanId', $Cat ) ) { |
| 67 | + $Cat['name'] = $this->CategorySpans[ $Cat['spanId'] ]['name'] . "\n" . $Cat['name']; |
| 68 | + unset( $Cat['spanId'] ); |
| 69 | + } |
| 70 | + } |
| 71 | + unset( $this->CategorySpans ); |
| 72 | + $this->CategorySpans = Array(); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + // restore spans from categories |
| 77 | + function restoreSpans() { |
| 78 | + if ( count( $this->CategorySpans ) == 0 ) { |
| 79 | + $prevSpanName = ''; |
| 80 | + $spanId = -1; |
| 81 | + foreach ( $this->Categories as &$Cat ) { |
| 82 | + $a = explode( "\n", $Cat['name'] ); |
| 83 | + if ( count( $a ) > 1 ) { |
| 84 | + if ( $prevSpanName != $a[0] ) { |
| 85 | + $spanId++; |
| 86 | + $prevSpanName = $a[0]; |
| 87 | + $this->CategorySpans[ $spanId ]['count'] = 0; |
| 88 | + } |
| 89 | + $Cat['name'] = $a[1]; |
| 90 | + $Cat['spanId'] = $spanId; |
| 91 | + $this->CategorySpans[ $spanId ]['name'] = $a[0]; |
| 92 | + $this->CategorySpans[ $spanId ]['count']++; |
| 93 | + } else { |
| 94 | + $prevSpanName = ''; |
| 95 | + } |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + // check whether the previousely stored poll header is compatible with the one defined on the page |
| 101 | + // used to reject previous vote in case the header is incompatble |
| 102 | + function isCompatible( &$question ) { |
| 103 | + if ( $question->mType != $this->type ) { |
| 104 | + return false; |
| 105 | + } |
| 106 | + if ( count( $question->mCategorySpans ) != count( $this->CategorySpans ) ) { |
| 107 | + return false; |
| 108 | + } |
| 109 | + foreach ( $question->mCategorySpans as $spanidx => &$span ) { |
| 110 | + if ( !isset( $this->CategorySpans[ $spanidx ] ) || |
| 111 | + $span[ "count" ] != $this->CategorySpans[ $spanidx ][ "count" ] ) { |
| 112 | + return false; |
| 113 | + } |
| 114 | + } |
| 115 | + return true; |
| 116 | + } |
| 117 | + |
| 118 | + private function categoryentities( $cat ) { |
| 119 | + $cat['name'] = qp_Setup::entities( $cat['name'] ); |
| 120 | + return $cat; |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * @return string html representation of user vote for Special:Pollresults output |
| 125 | + */ |
| 126 | + function displayUserQuestionVote() { |
| 127 | + $output = "<div class=\"qpoll\">\n" . "<table class=\"pollresults\">\n"; |
| 128 | + $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $this->CategorySpans ), array( 'class' => 'spans' ), 'th', array( 'count' => 'colspan', 'name' => 0 ) ); |
| 129 | + $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $this->Categories ), '', 'th', array( 'name' => 0 ) ); |
| 130 | + # multiple choice polls doesn't use real spans, instead, every column is like "span" |
| 131 | + $spansUsed = count( $this->CategorySpans ) > 0 || $this->type == "multipleChoice"; |
| 132 | + foreach ( $this->ProposalText as $propkey => &$proposal_text ) { |
| 133 | + $row = Array(); |
| 134 | + foreach ( $this->Categories as $catkey => &$cat_name ) { |
| 135 | + $cell = Array( 0 => "" ); |
| 136 | + if ( array_key_exists( $propkey, $this->ProposalCategoryId ) && |
| 137 | + ( $id_key = array_search( $catkey, $this->ProposalCategoryId[ $propkey ] ) ) !== false ) { |
| 138 | + $text_answer = $this->ProposalCategoryText[ $propkey ][ $id_key ]; |
| 139 | + if ( $text_answer != '' ) { |
| 140 | + if ( strlen( $text_answer ) > 20 ) { |
| 141 | + $cell[ 0 ] = array( '__tag' => 'div', 'style' => 'width:10em; height:5em; overflow:auto', 0 => qp_Setup::entities( $text_answer ) ); |
| 142 | + } else { |
| 143 | + $cell[ 0 ] = qp_Setup::entities( $text_answer ); |
| 144 | + } |
| 145 | + } else { |
| 146 | + $cell[ 0 ] = '+'; |
| 147 | + } |
| 148 | + } |
| 149 | + if ( $spansUsed ) { |
| 150 | + if ( $this->type == "multipleChoice" ) { |
| 151 | + $cell[ "class" ] = ( ( $catkey & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
| 152 | + } else { |
| 153 | + $cell[ "class" ] = ( ( $this->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
| 154 | + } |
| 155 | + } else { |
| 156 | + $cell[ "class" ] = "stats"; |
| 157 | + } |
| 158 | + $row[] = $cell; |
| 159 | + } |
| 160 | + $row[] = array( 0 => qp_Setup::entities( $proposal_text ), "style" => "text-align:left;" ); |
| 161 | + $output .= qp_Renderer::displayRow( $row ); |
| 162 | + } |
| 163 | + $output .= "</table>\n" . "</div>\n"; |
| 164 | + return $output; |
| 165 | + } |
| 166 | + |
| 167 | + /** |
| 168 | + * @return string html representation of question statistics for Special:Pollresults output |
| 169 | + */ |
| 170 | + function displayQuestionStats( qp_SpecialPage $page, $pid ) { |
| 171 | + $current_title = $page->getTitle(); |
| 172 | + $output = "<br />\n<b>" . $this->question_id . ".</b> " . qp_Setup::entities( $this->CommonQuestion ) . "<br />\n"; |
| 173 | + $output .= "<div class=\"qpoll\">\n" . "<table class=\"pollresults\">\n"; |
| 174 | + $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $this->CategorySpans ), array( 'class' => 'spans' ), 'th', array( 'count' => 'colspan', 'name' => 0 ) ); |
| 175 | + $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $this->Categories ), '', 'th', array( 'name' => 0 ) ); |
| 176 | + # multiple choice polls doesn't use real spans, instead, every column is like "span" |
| 177 | + $spansUsed = count( $this->CategorySpans ) > 0 || $this->type == "multipleChoice"; |
| 178 | + foreach ( $this->ProposalText as $propkey => &$proposal_text ) { |
| 179 | + if ( isset( $this->Votes[ $propkey ] ) ) { |
| 180 | + if ( $this->Percents === null ) { |
| 181 | + $row = $this->Votes[ $propkey ]; |
| 182 | + } else { |
| 183 | + $row = $this->Percents[ $propkey ]; |
| 184 | + foreach ( $row as $catkey => &$cell ) { |
| 185 | + # Replace spaces with en spaces |
| 186 | + $formatted_cell = str_replace( " ", " ", sprintf( '%3d%%', intval( round( 100 * $cell ) ) ) ); |
| 187 | + # only percents !=0 are displayed as link |
| 188 | + if ( $cell == 0.0 && $this->question_id !== null ) { |
| 189 | + $cell = array( 0 => $formatted_cell, "style" => "color:gray" ); |
| 190 | + } else { |
| 191 | + $cell = array( 0 => $page->qpLink( $current_title, $formatted_cell, |
| 192 | + array( "title" => wfMsgExt( 'qp_votes_count', array( 'parsemag' ), $this->Votes[ $propkey ][ $catkey ] ) ), |
| 193 | + array( "action" => "qpcusers", "id" => $pid, "qid" => $this->question_id, "pid" => $propkey, "cid" => $catkey ) ) ); |
| 194 | + } |
| 195 | + if ( $spansUsed ) { |
| 196 | + if ( $this->type == "multipleChoice" ) { |
| 197 | + $cell[ "class" ] = ( ( $catkey & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
| 198 | + } else { |
| 199 | + $cell[ "class" ] = ( ( $this->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
| 200 | + } |
| 201 | + } else { |
| 202 | + $cell[ "class" ] = "stats"; |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + } else { |
| 207 | + # this proposal has no statistics (no votes) |
| 208 | + $row = array_fill( 0, count( $this->Categories ), '' ); |
| 209 | + } |
| 210 | + $row[] = array( 0 => qp_Setup::entities( $proposal_text ), "style" => "text-align:left;" ); |
| 211 | + $output .= qp_Renderer::displayRow( $row ); |
| 212 | + } |
| 213 | + $output .= "</table>\n" . "</div>\n"; |
| 214 | + return $output; |
| 215 | + } |
| 216 | + |
| 217 | +} /* end of qp_QuestionData class */ |
| 218 | + |
| 219 | +/** |
| 220 | + * Questions of type="text" require a different view logic at the Special:Pollresults page |
| 221 | + */ |
| 222 | +class qp_TextQuestionData extends qp_QuestionData { |
| 223 | + |
| 224 | +/** |
| 225 | + * Please do not instantiate directly, use qp_PollStore::newQuestionData() instead |
| 226 | + */ |
| 227 | + |
| 228 | + /** |
| 229 | + * @return string html representation of user vote for Special:Pollresults output |
| 230 | + */ |
| 231 | + function displayUserQuestionVote() { |
| 232 | + $output = "<div class=\"qpoll\">\n" . "<table class=\"pollresults\">\n"; |
| 233 | + foreach ( $this->ProposalText as $propkey => &$serialized_tokens ) { |
| 234 | + if ( !is_array( $dbtokens = unserialize( $serialized_tokens ) ) ) { |
| 235 | + throw new MWException( 'dbtokens is not an array in ' . __METHOD__ ); |
| 236 | + } |
| 237 | + $catId = 0; |
| 238 | + $row = array(); |
| 239 | + foreach ( $dbtokens as &$token ) { |
| 240 | + if ( is_string( $token ) ) { |
| 241 | + # add a proposal part |
| 242 | + $row[] = array( '__tag' => 'span', 'class' => 'prop_part', qp_Setup::entities( $token ) ); |
| 243 | + } elseif ( is_array( $token ) ) { |
| 244 | + # add a category definition with selected text answer (if any) |
| 245 | + if ( array_key_exists( $propkey, $this->ProposalCategoryId ) && |
| 246 | + ( $id_key = array_search( $catId, $this->ProposalCategoryId[$propkey] ) ) !== false ) { |
| 247 | + $text_answer = $this->ProposalCategoryText[$propkey][$id_key]; |
| 248 | + } else { |
| 249 | + $text_answer = ''; |
| 250 | + } |
| 251 | + $className = ( count( $token ) === 1 || in_array( $text_answer, $token ) ) ? 'cat_part' : 'cat_unknown'; |
| 252 | + $titleAttr = ''; |
| 253 | + foreach ( $token as &$option ) { |
| 254 | + if ( $option !== $text_answer ) { |
| 255 | + if ( $titleAttr !== '' ) { |
| 256 | + $titleAttr .= ' | '; |
| 257 | + } |
| 258 | + $titleAttr .= qp_Setup::entities( $option ); |
| 259 | + } |
| 260 | + } |
| 261 | + $row[] = array( '__tag' => 'span', 'class' => $className, 'title'=>$titleAttr, qp_Setup::entities( $text_answer ) ); |
| 262 | + # move to the next category (if any) |
| 263 | + $catId++; |
| 264 | + } else { |
| 265 | + throw new MWException( 'DB token has invalid type (' . gettype( $token ) . ') in ' . __METHOD__ ); |
| 266 | + } |
| 267 | + } |
| 268 | + $output .= qp_Renderer::displayRow( array( $row ) ); |
| 269 | + } |
| 270 | + $output .= "</table>\n" . "</div>\n"; |
| 271 | + return $output; |
| 272 | + } |
| 273 | + |
| 274 | +} /* end of qp_TextQuestionData class */ |
Property changes on: trunk/extensions/QPoll/qp_questiondata.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 275 | + native |
Index: trunk/extensions/QPoll/qp_results.php |
— | — | @@ -360,58 +360,13 @@ |
361 | 361 | foreach ( $pollStore->Questions as &$qdata ) { |
362 | 362 | if ( $pollStore->isUsedQuestion( $qdata->question_id ) ) { |
363 | 363 | $output .= "<br />\n<b>" . $qdata->question_id . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n"; |
364 | | - $output .= $this->displayUserQuestionVote( $qdata ); |
| 364 | + $qview = |
| 365 | + $output .= $qdata->displayUserQuestionVote(); |
365 | 366 | } |
366 | 367 | } |
367 | 368 | return $output; |
368 | 369 | } |
369 | 370 | |
370 | | - private function categoryentities( $cat ) { |
371 | | - $cat['name'] = qp_Setup::entities( $cat['name'] ); |
372 | | - return $cat; |
373 | | - } |
374 | | - |
375 | | - private function displayUserQuestionVote( &$qdata ) { |
376 | | - $output = "<div class=\"qpoll\">\n" . "<table class=\"pollresults\">\n"; |
377 | | - $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $qdata->CategorySpans ), array( 'class' => 'spans' ), 'th', array( 'count' => 'colspan', 'name' => 0 ) ); |
378 | | - $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $qdata->Categories ), '', 'th', array( 'name' => 0 ) ); |
379 | | - # multiple choice polls doesn't use real spans, instead, every column is like "span" |
380 | | - $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice"; |
381 | | - foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) { |
382 | | - $row = Array(); |
383 | | - foreach ( $qdata->Categories as $catkey => &$cat_name ) { |
384 | | - $cell = Array( 0 => "" ); |
385 | | - if ( array_key_exists( $propkey, $qdata->ProposalCategoryId ) && |
386 | | - ( $id_key = array_search( $catkey, $qdata->ProposalCategoryId[ $propkey ] ) ) !== false ) { |
387 | | - $text_answer = $qdata->ProposalCategoryText[ $propkey ][ $id_key ]; |
388 | | - if ( $text_answer != '' ) { |
389 | | - if ( strlen( $text_answer ) > 20 ) { |
390 | | - $cell[ 0 ] = array( '__tag' => 'div', 'style' => 'width:10em; height:5em; overflow:auto', 0 => qp_Setup::entities( $text_answer ) ); |
391 | | - } else { |
392 | | - $cell[ 0 ] = qp_Setup::entities( $text_answer ); |
393 | | - } |
394 | | - } else { |
395 | | - $cell[ 0 ] = '+'; |
396 | | - } |
397 | | - } |
398 | | - if ( $spansUsed ) { |
399 | | - if ( $qdata->type == "multipleChoice" ) { |
400 | | - $cell[ "class" ] = ( ( $catkey & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
401 | | - } else { |
402 | | - $cell[ "class" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
403 | | - } |
404 | | - } else { |
405 | | - $cell[ "class" ] = "stats"; |
406 | | - } |
407 | | - $row[] = $cell; |
408 | | - } |
409 | | - $row[] = array( 0 => qp_Setup::entities( $proposal_text ), "style" => "text-align:left;" ); |
410 | | - $output .= qp_Renderer::displayRow( $row ); |
411 | | - } |
412 | | - $output .= "</table>\n" . "</div>\n"; |
413 | | - return $output; |
414 | | - } |
415 | | - |
416 | 371 | private function showVotes( $pid ) { |
417 | 372 | $output = ""; |
418 | 373 | if ( $pid !== null ) { |
— | — | @@ -426,9 +381,8 @@ |
427 | 382 | $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n"; |
428 | 383 | $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'stats_xls', 'id' => $pid ) ) . "<br />\n"; |
429 | 384 | $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_voices_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'voices_xls', 'id' => $pid ) ) . "<br />\n"; |
430 | | - foreach ( $pollStore->Questions as $qkey => &$qdata ) { |
431 | | - $output .= "<br />\n<b>" . $qkey . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n"; |
432 | | - $output .= $this->displayQuestionStats( $pid, $qdata ); |
| 385 | + foreach ( $pollStore->Questions as &$qdata ) { |
| 386 | + $output .= $qdata->displayQuestionStats( $this, $pid ); |
433 | 387 | } |
434 | 388 | } |
435 | 389 | } |
— | — | @@ -649,52 +603,6 @@ |
650 | 604 | } |
651 | 605 | } |
652 | 606 | |
653 | | - private function displayQuestionStats( $pid, &$qdata ) { |
654 | | - $current_title = $this->getTitle(); |
655 | | - $output = "<div class=\"qpoll\">\n" . "<table class=\"pollresults\">\n"; |
656 | | - $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $qdata->CategorySpans ), array( 'class' => 'spans' ), 'th', array( 'count' => 'colspan', 'name' => 0 ) ); |
657 | | - $output .= qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $qdata->Categories ), '', 'th', array( 'name' => 0 ) ); |
658 | | - # multiple choice polls doesn't use real spans, instead, every column is like "span" |
659 | | - $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice"; |
660 | | - foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) { |
661 | | - if ( isset( $qdata->Votes[ $propkey ] ) ) { |
662 | | - if ( $qdata->Percents === null ) { |
663 | | - $row = $qdata->Votes[ $propkey ]; |
664 | | - } else { |
665 | | - $row = $qdata->Percents[ $propkey ]; |
666 | | - foreach ( $row as $catkey => &$cell ) { |
667 | | - # Replace spaces with en spaces |
668 | | - $formatted_cell = str_replace( " ", " ", sprintf( '%3d%%', intval( round( 100 * $cell ) ) ) ); |
669 | | - # only percents !=0 are displayed as link |
670 | | - if ( $cell == 0.0 && $qdata->question_id !== null ) { |
671 | | - $cell = array( 0 => $formatted_cell, "style" => "color:gray" ); |
672 | | - } else { |
673 | | - $cell = array( 0 => $this->qpLink( $current_title, $formatted_cell, |
674 | | - array( "title" => wfMsgExt( 'qp_votes_count', array( 'parsemag' ), $qdata->Votes[ $propkey ][ $catkey ] ) ), |
675 | | - array( "action" => "qpcusers", "id" => $pid, "qid" => $qdata->question_id, "pid" => $propkey, "cid" => $catkey ) ) ); |
676 | | - } |
677 | | - if ( $spansUsed ) { |
678 | | - if ( $qdata->type == "multipleChoice" ) { |
679 | | - $cell[ "class" ] = ( ( $catkey & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
680 | | - } else { |
681 | | - $cell[ "class" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? "spaneven" : "spanodd"; |
682 | | - } |
683 | | - } else { |
684 | | - $cell[ "class" ] = "stats"; |
685 | | - } |
686 | | - } |
687 | | - } |
688 | | - } else { |
689 | | - # this proposal has no statistics (no votes) |
690 | | - $row = array_fill( 0, count( $qdata->Categories ), '' ); |
691 | | - } |
692 | | - $row[] = array( 0 => qp_Setup::entities( $proposal_text ), "style" => "text-align:left;" ); |
693 | | - $output .= qp_Renderer::displayRow( $row ); |
694 | | - } |
695 | | - $output .= "</table>\n" . "</div>\n"; |
696 | | - return $output; |
697 | | - } |
698 | | - |
699 | 607 | static function getUsersLink() { |
700 | 608 | return "<div>" . self::$UsersLink . "</div>\n"; |
701 | 609 | } |
— | — | @@ -1023,7 +931,7 @@ |
1024 | 932 | $head[] = PollResults::getUsersLink(); |
1025 | 933 | $head[] = array( '__tag' => 'div', 'class' => 'head', 0 => $spec ); |
1026 | 934 | $head[] = ' (' . $goto_link . ')'; |
1027 | | - $link = qp_Renderer::renderHTMLobject( $head ); |
| 935 | + $link = qp_Renderer::renderTagArray( $head ); |
1028 | 936 | } |
1029 | 937 | return $link; |
1030 | 938 | } |
— | — | @@ -1139,7 +1047,7 @@ |
1140 | 1048 | qp_Setup::entities( $proptext ), |
1141 | 1049 | qp_Setup::entities( $cat_name ) ) . '<br />'; |
1142 | 1050 | $head[] = array( '__tag' => 'div', 'class' => 'head', 'style' => 'padding-left:2em;', 0 => $qpa ); |
1143 | | - $link = qp_Renderer::renderHTMLobject( $head ); |
| 1051 | + $link = qp_Renderer::renderTagArray( $head ); |
1144 | 1052 | } |
1145 | 1053 | } |
1146 | 1054 | } |
Index: trunk/extensions/QPoll/ctrl/qp_abstractquestion.php |
— | — | @@ -41,15 +41,13 @@ |
42 | 42 | * @public |
43 | 43 | * @param $poll an instance of question's parent controller |
44 | 44 | * @param $view an instance of question view "linked" to this question |
45 | | - * @param $questionId the identifier of the question used to gernerate input names |
| 45 | + * @param $questionId the identifier of the question used to generate input names |
46 | 46 | */ |
47 | 47 | function __construct( qp_AbstractPoll $poll, qp_AbstractView $view, $questionId ) { |
48 | 48 | global $wgRequest; |
49 | 49 | $this->mRequest = &$wgRequest; |
50 | 50 | # the question collection is not sparce by default |
51 | 51 | $this->mQuestionId = $this->usedId = $questionId; |
52 | | - $this->mProposalPattern = '`^[^\|\!].*`u'; |
53 | | - $this->mCategoryPattern = '`^\|(\n|[^\|].*\n)`u'; |
54 | 52 | $view->setController( $this ); |
55 | 53 | $this->view = $view; |
56 | 54 | $this->poll = $poll; |
— | — | @@ -67,7 +65,7 @@ |
68 | 66 | $this->mState = $pState; |
69 | 67 | } |
70 | 68 | if ( $error_message !== null ) { |
71 | | - # store header error message that cannot be output now, but will be displayed at later stage |
| 69 | + # store header error message that cannot be output now, but will be displayed at rendering stage |
72 | 70 | $this->view->headerErrorMessage = $error_message; |
73 | 71 | } |
74 | 72 | } |
Index: trunk/extensions/QPoll/ctrl/qp_textquestion.php |
— | — | @@ -0,0 +1,268 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 5 | + die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
| 6 | +} |
| 7 | + |
| 8 | +/** |
| 9 | + * Stores the list of current category options - |
| 10 | + * usually the pipe-separated entries in double angle brackets list |
| 11 | + */ |
| 12 | +class qp_TextQuestionOptions { |
| 13 | + |
| 14 | + # boolean, indicates whether incoming tokens are category list elements |
| 15 | + var $isCatDef; |
| 16 | + # counter of pipe-separated elements in-between << >> markup |
| 17 | + # used to distinguish real category options from textwidth definition |
| 18 | + var $catDefIdx; |
| 19 | + # list of input options; array whose every element is a string |
| 20 | + var $input_options; |
| 21 | + # a value of textwidth definition for input text field |
| 22 | + # it is defined as first element of options list, for example: |
| 23 | + # <<::12>> or <<::15|test>> |
| 24 | + # currently, it is used only for text inputs (not for select/option list) |
| 25 | + var $textwidth; |
| 26 | + # a pointer to last element in $this->input_options array |
| 27 | + var $iopt_last; |
| 28 | + |
| 29 | + /** |
| 30 | + * Prepare options for new proposal line |
| 31 | + */ |
| 32 | + function reset() { |
| 33 | + $this->isCatDef = false; |
| 34 | + $this->input_options = array(); |
| 35 | + $this->catDefIdx = 0; |
| 36 | + } |
| 37 | + |
| 38 | + /** |
| 39 | + * Creates first single empty option |
| 40 | + * Applies default settings to the options list |
| 41 | + * New category begins |
| 42 | + */ |
| 43 | + function startOptionsList() { |
| 44 | + $this->isCatDef = true; |
| 45 | + $this->input_options = array( 0 => '' ); |
| 46 | + $this->textwidth = null; // will use default value |
| 47 | + $this->iopt_last = &$this->input_options[0]; |
| 48 | + } |
| 49 | + |
| 50 | + /** |
| 51 | + * Adds new empty option |
| 52 | + * This option will be "current last option" |
| 53 | + */ |
| 54 | + function addEmptyOption() { |
| 55 | + # add new empty option only if there was no textwidth definition |
| 56 | + if ( is_null( $this->textwidth ) || $this->catDefIdx !== 0 ) { |
| 57 | + # add new empty option to the end of the list |
| 58 | + $this->input_options[] = ''; |
| 59 | + $this->iopt_last = &$this->input_options[count( $this->input_options ) - 1]; |
| 60 | + } |
| 61 | + $this->catDefIdx++; |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Set string value to current last option |
| 66 | + * @param $token string current value of token between pipe separators |
| 67 | + * Also, _optionally_ overrides textwidth property |
| 68 | + */ |
| 69 | + function setLastOption( $token ) { |
| 70 | + # first entry of category options might be definition of |
| 71 | + # the current category input textwidth instead |
| 72 | + $matches = array(); |
| 73 | + if ( count( $this->input_options ) === 1 && |
| 74 | + preg_match( '`^\s*::(\d{1,2})\s*$`', $token, $matches ) && |
| 75 | + $matches[1] > 0 ) { |
| 76 | + # override the textwidth of input options |
| 77 | + $this->textwidth = intval( $matches[1] ); |
| 78 | + } else { |
| 79 | + # add new input option |
| 80 | + $this->iopt_last .= trim( $token ); |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + /** |
| 85 | + * Closes current category definition and prepares input options entries |
| 86 | + */ |
| 87 | + function closeCategory() { |
| 88 | + $this->isCatDef = false; |
| 89 | + # prepare new category input choice (text questions have no category names) |
| 90 | + $this->input_options = array_unique( $this->input_options, SORT_STRING ); |
| 91 | + # make sure elements keys are consequitive starting from 0 |
| 92 | + sort( $this->input_options, SORT_STRING ); |
| 93 | + } |
| 94 | + |
| 95 | +} /* end of qp_TextQuestionOptions class */ |
| 96 | + |
| 97 | +/** |
| 98 | + * A base class for parsing, checking and visualisation of text questions in |
| 99 | + * declaration/voting mode (UI input/output) |
| 100 | + * |
| 101 | + * An attempt to make somewhat cleaner question controller |
| 102 | + * todo: further refactoring of controllers for different question types |
| 103 | + */ |
| 104 | +class qp_TextQuestion extends qp_StubQuestion { |
| 105 | + |
| 106 | + # not longer than qp_question_proposals.proposal_text (currently, tinytext) |
| 107 | + const MAX_PROPOSAL_LENGTH = 255; |
| 108 | + const PROP_CAT_PATTERN = '`(<<|>>|{{|}}|\[\[|\]\]|\|)`u'; |
| 109 | + |
| 110 | + # $viewtokens is an instance of qp_TextQuestionViewTokens |
| 111 | + # which contains parsed tokens for combined |
| 112 | + # proposal/category view |
| 113 | + var $viewtokens; |
| 114 | + # $dbtokens will contain parsed tokens for combined |
| 115 | + # proposal/category storage |
| 116 | + # $dbtokens elements do not include error messages; |
| 117 | + # only proposal parts and category options |
| 118 | + var $dbtokens = array(); |
| 119 | + |
| 120 | + /** |
| 121 | + * Parses question body header. |
| 122 | + * Text questions do not have "body header" (no definitions of spans and categories) |
| 123 | + * so, this method just splits raw lines of body text to analyze raws in $this->parseBody() |
| 124 | + * @param $input - the text of question body |
| 125 | + */ |
| 126 | + function parseBodyHeader( $input ) { |
| 127 | + $this->raws = preg_split( '`\n`su', $input, -1, PREG_SPLIT_NO_EMPTY ); |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Load text answer to the selected (proposal,category) pair, when available |
| 132 | + * Also, stores text answer into the parsed tokens list (viewtokens) |
| 133 | + */ |
| 134 | + function loadProposalCategory( qp_TextQuestionOptions $opt, $proposalId, $catId ) { |
| 135 | + $name = "q{$this->mQuestionId}p{$proposalId}s{$catId}"; |
| 136 | + $text_answer = ''; |
| 137 | + $user_answered = false; |
| 138 | + # try to load from POST data |
| 139 | + if ( $this->poll->mBeingCorrected && $this->mRequest->getVal( $name ) !== null ) { |
| 140 | + $text_answer = trim( $this->mRequest->getText( $name ) ); |
| 141 | + } |
| 142 | + if ( strlen( $text_answer ) > qp_Setup::MAX_TEXT_ANSWER_LENGTH ) { |
| 143 | + $text_answer = substr( $text_answer, 0, qp_Setup::MAX_TEXT_ANSWER_LENGTH ); |
| 144 | + } |
| 145 | + if ( $text_answer != '' ) { |
| 146 | + $user_answered = true; |
| 147 | + } |
| 148 | + # try to load from pollStore |
| 149 | + # pollStore optionally overrides POST data |
| 150 | + $prev_text_answer = $this->answerExists( 'text', $proposalId, $catId ); |
| 151 | + if ( $prev_text_answer !== false ) { |
| 152 | + $user_answered = true; |
| 153 | + $text_answer = $prev_text_answer; |
| 154 | + } |
| 155 | + if ( $user_answered ) { |
| 156 | + # add category to the list of user answers for current proposal (row) |
| 157 | + $this->mProposalCategoryId[ $proposalId ][] = $catId; |
| 158 | + $this->mProposalCategoryText[ $proposalId ][] = $text_answer; |
| 159 | + } |
| 160 | + # finally, add new category input options for the view |
| 161 | + $opt->closeCategory(); |
| 162 | + $this->viewtokens->addCatDef( $opt, $name, $text_answer, $this->poll->mBeingCorrected && !$user_answered ); |
| 163 | + } |
| 164 | + |
| 165 | + /** |
| 166 | + * Creates question view which should be renreded and |
| 167 | + * also may be altered during the poll generation |
| 168 | + */ |
| 169 | + function parseBody() { |
| 170 | + $matching_braces = array( |
| 171 | + '[[' => ']]', |
| 172 | + '{{' => '}}', |
| 173 | + '<<' => '>>' |
| 174 | + ); |
| 175 | + $proposalId = 0; |
| 176 | + # Currently, we use just a single instance (no nested categories) |
| 177 | + $opt = new qp_TextQuestionOptions(); |
| 178 | + $this->viewtokens = new qp_TextQuestionViewTokens( $this->view ); |
| 179 | + foreach ( $this->raws as $raw ) { |
| 180 | + $this->view->initProposalView(); |
| 181 | + $opt->reset(); |
| 182 | + $this->viewtokens->reset(); |
| 183 | + $this->dbtokens = $brace_stack = array(); |
| 184 | + $catId = 0; |
| 185 | + $last_brace = ''; |
| 186 | + $tokens = preg_split( self::PROP_CAT_PATTERN, $raw, -1, PREG_SPLIT_DELIM_CAPTURE ); |
| 187 | + foreach ( $tokens as $token ) { |
| 188 | + $isContinue = false; |
| 189 | + switch ( $token ) { |
| 190 | + case '|' : |
| 191 | + if ( $opt->isCatDef ) { |
| 192 | + $opt->addEmptyOption(); |
| 193 | + $isContinue = true; |
| 194 | + } |
| 195 | + break; |
| 196 | + case '[[' : |
| 197 | + case '{{' : |
| 198 | + case '<<' : |
| 199 | + array_push( $brace_stack, $matching_braces[$token] ); |
| 200 | + if ( $token === '<<' && count( $brace_stack ) == 1 ) { |
| 201 | + $opt->startOptionsList(); |
| 202 | + $isContinue = true; |
| 203 | + } |
| 204 | + break; |
| 205 | + case ']]' : |
| 206 | + case '}}' : |
| 207 | + case '>>' : |
| 208 | + if ( count( $brace_stack ) > 0 ) { |
| 209 | + $last_brace = array_pop( $brace_stack ); |
| 210 | + if ( $last_brace != $token ) { |
| 211 | + array_push( $brace_stack, $last_brace ); |
| 212 | + break; |
| 213 | + } |
| 214 | + if ( count( $brace_stack ) > 0 || $token !== '>>' ) { |
| 215 | + break; |
| 216 | + } |
| 217 | + # add new category input options for the storage |
| 218 | + $this->dbtokens[] = $opt->input_options; |
| 219 | + # setup mCategories |
| 220 | + $this->mCategories[$catId] = array( 'name' => strval( $catId ) ); |
| 221 | + # load proposal/category answer (when available) |
| 222 | + $this->loadProposalCategory( $opt, $proposalId, $catId ); |
| 223 | + # current category is over |
| 224 | + $catId++; |
| 225 | + $isContinue = true; |
| 226 | + } |
| 227 | + break; |
| 228 | + } |
| 229 | + if ( $isContinue ) { |
| 230 | + continue; |
| 231 | + } |
| 232 | + if ( $opt->isCatDef ) { |
| 233 | + $opt->setLastOption( $token ); |
| 234 | + } else { |
| 235 | + # add new proposal part |
| 236 | + $this->dbtokens[] = strval( $token ); |
| 237 | + $this->viewtokens->addProposalPart( $token ); |
| 238 | + } |
| 239 | + } |
| 240 | + # check if there is at least one category defined |
| 241 | + if ( $catId === 0 ) { |
| 242 | + # todo: this is the explanary line, it is not real proposal |
| 243 | + $this->viewtokens->prependErrorToken( wfMsg( 'qp_error_too_few_categories' ), 'error' ); |
| 244 | + } |
| 245 | + if ( strlen( $proposal_text = serialize( $this->dbtokens ) ) > self::MAX_PROPOSAL_LENGTH ) { |
| 246 | + # too long proposal field to store into the DB |
| 247 | + # this is very important check for text questions because |
| 248 | + # category definitions are stored within the proposal text |
| 249 | + $this->viewtokens->prependErrorToken( wfMsg( 'qp_error_too_long_proposal_text' ), 'error' ); |
| 250 | + } |
| 251 | + $this->mProposalText[$proposalId] = $proposal_text; |
| 252 | + # If the proposal was submitted but has _any_ unanswered category |
| 253 | + if ( $this->poll->mBeingCorrected && |
| 254 | + ( !array_key_exists( $proposalId, $this->mProposalCategoryId ) || |
| 255 | + count( $this->mProposalCategoryId[$proposalId] ) !== $catId ) |
| 256 | + ) { |
| 257 | + # todo: apply 'error' style to the whole row |
| 258 | + $prev_state = $this->getState(); |
| 259 | + $this->viewtokens->prependErrorToken( wfMsg( 'qp_error_no_answer' ), 'NA' ); |
| 260 | + if ( $prev_state == '' ) { |
| 261 | + # todo: if there was no previous errors, hightlight the whole row |
| 262 | + } |
| 263 | + } |
| 264 | + $this->view->addProposal( $proposalId, $this->viewtokens->tokenslist ); |
| 265 | + $proposalId++; |
| 266 | + } |
| 267 | + } |
| 268 | + |
| 269 | +} /* end of qp_TextQuestion class */ |
Property changes on: trunk/extensions/QPoll/ctrl/qp_textquestion.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 270 | + native |
Index: trunk/extensions/QPoll/ctrl/qp_mixedquestion.php |
— | — | @@ -5,7 +5,7 @@ |
6 | 6 | } |
7 | 7 | |
8 | 8 | /** |
9 | | - * A base class for parsing, checking ans visualisation of _mixed_ tabular questions in |
| 9 | + * A base class for parsing, checking and visualisation of _mixed_ tabular questions in |
10 | 10 | * declaration/voting mode (UI input/output) |
11 | 11 | */ |
12 | 12 | class qp_MixedQuestion extends qp_TabularQuestion { |
— | — | @@ -15,7 +15,6 @@ |
16 | 16 | * also may be altered during the poll generation |
17 | 17 | */ |
18 | 18 | function parseBody() { |
19 | | - # Parameters used in some special cases. |
20 | 19 | $this->mProposalPattern = '`^'; |
21 | 20 | foreach ( $this->mCategories as $catDesc ) { |
22 | 21 | $this->mProposalPattern .= '(\[\]|\(\)|<>)'; |
Index: trunk/extensions/QPoll/ctrl/qp_tabularquestion.php |
— | — | @@ -5,19 +5,29 @@ |
6 | 6 | } |
7 | 7 | |
8 | 8 | /** |
9 | | - * A base class for parsing, checking ans visualisation of tabular/mixed questions in |
| 9 | + * A base class for parsing, checking and visualisation of tabular questions in |
10 | 10 | * declaration/voting mode (UI input/output) |
11 | 11 | */ |
12 | 12 | class qp_TabularQuestion extends qp_StubQuestion { |
13 | 13 | |
14 | | - # build categories and spans internal & visual representations according to |
15 | | - # definition of categories and spans (AKA metacategories) in the question body |
16 | | - # @param $input - the text of question body |
17 | | - # |
18 | | - # internally, the header is split into |
19 | | - # main header (part inside curly braces) and |
20 | | - # body header (categories and metacategories defitions) |
21 | | - # |
| 14 | + /** |
| 15 | + * Constructor |
| 16 | + * @public |
| 17 | + * @param $poll an instance of question's parent controller |
| 18 | + * @param $view an instance of question view "linked" to this question |
| 19 | + * @param $questionId the identifier of the question used to generate input names |
| 20 | + */ |
| 21 | + function __construct( qp_AbstractPoll $poll, qp_AbstractView $view, $questionId ) { |
| 22 | + parent::__construct( $poll, $view, $questionId ); |
| 23 | + $this->mProposalPattern = '`^[^\|\!].*`u'; |
| 24 | + $this->mCategoryPattern = '`^\|(\n|[^\|].*\n)`u'; |
| 25 | + } |
| 26 | + |
| 27 | + /** |
| 28 | + * Builds internal & visual representations of categories and spans according to their |
| 29 | + * text definition in the question body |
| 30 | + * @param $input - the text of question body |
| 31 | + */ |
22 | 32 | function parseBodyHeader( $input ) { |
23 | 33 | $this->raws = preg_split( '`\n`su', $input, -1, PREG_SPLIT_NO_EMPTY ); |
24 | 34 | $categorySpans = false; |
— | — | @@ -119,7 +129,7 @@ |
120 | 130 | * @param $input the raw source of category spans |
121 | 131 | */ |
122 | 132 | # warning: parseCategorySpans() should be called after parseCategories() |
123 | | - # todo: split view logic into qp_QuestionView class |
| 133 | + # todo: split view logic into the associated view class |
124 | 134 | function parseCategorySpans( $input ) { |
125 | 135 | $row = array(); |
126 | 136 | if ( $this->mType != 'singleChoice' ) { |
— | — | @@ -257,7 +267,6 @@ |
258 | 268 | * also may be altered during the poll generation |
259 | 269 | */ |
260 | 270 | function questionParseBody( $inputType ) { |
261 | | - # Parameters used in some special cases. |
262 | 271 | $proposalId = -1; |
263 | 272 | foreach ( $this->raws as $raw ) { |
264 | 273 | if ( !preg_match( $this->mProposalPattern, $raw, $matches ) ) { |
Index: trunk/extensions/QPoll/ctrl/qp_stubquestion.php |
— | — | @@ -19,6 +19,17 @@ |
20 | 20 | var $mPrevProposalCategoryId = Array(); // user true/false answers to the question's proposal from DB |
21 | 21 | var $mPrevProposalCategoryText = Array(); // user text answers to the question's proposal from DB |
22 | 22 | |
| 23 | + /** |
| 24 | + * Constructor |
| 25 | + * @public |
| 26 | + * @param $poll an instance of question's parent controller |
| 27 | + * @param $view an instance of question view "linked" to this question |
| 28 | + * @param $questionId the identifier of the question used to generate input names |
| 29 | + */ |
| 30 | + function __construct( qp_AbstractPoll $poll, qp_AbstractView $view, $questionId ) { |
| 31 | + parent::__construct( $poll, $view, $questionId ); |
| 32 | + } |
| 33 | + |
23 | 34 | # load some question fields from qp_QuestionData given |
24 | 35 | # (usually qp_QuestionData is an array property of qp_PollStore instance) |
25 | 36 | # @param $qdata - an instance of qp_QuestionData |
— | — | @@ -80,7 +91,7 @@ |
81 | 92 | # @param the object of type qp_PollStore |
82 | 93 | function store( qp_PollStore &$pollStore ) { |
83 | 94 | if ( $pollStore->pid !== null ) { |
84 | | - $pollStore->Questions[ $this->mQuestionId ] = new qp_QuestionData( array( |
| 95 | + $pollStore->Questions[ $this->mQuestionId ] = qp_PollStore::newQuestionData( array( |
85 | 96 | 'from' => 'postdata', |
86 | 97 | 'type' => $this->mType, |
87 | 98 | 'common_question' => $this->mCommonQuestion, |
Index: trunk/extensions/QPoll/ctrl/qp_poll.php |
— | — | @@ -380,7 +380,7 @@ |
381 | 381 | if ( $error_msg !== '' ) { |
382 | 382 | $question = new qp_StubQuestion( |
383 | 383 | $this, |
384 | | - qp_QuestionView::newFromBaseView( $this->view ), |
| 384 | + qp_StubQuestionView::newFromBaseView( $this->view ), |
385 | 385 | ++$this->mQuestionId |
386 | 386 | ); |
387 | 387 | $question->setState( 'error', $error_msg ); |
— | — | @@ -388,9 +388,9 @@ |
389 | 389 | } |
390 | 390 | |
391 | 391 | $qt = qp_Setup::$questionTypes[$type]; |
392 | | - $question = new $qt['className']( |
| 392 | + $question = new $qt['ctrl']( |
393 | 393 | $this, |
394 | | - qp_QuestionView::newFromBaseView( $this->view ), |
| 394 | + call_user_func( array( $qt['view'], 'newFromBaseView' ), $this->view ), |
395 | 395 | ++$this->mQuestionId |
396 | 396 | ); |
397 | 397 | # set the question type and subtype corresponding to the header 'type' attribute |
— | — | @@ -435,7 +435,7 @@ |
436 | 436 | /** |
437 | 437 | * Populates the question with data and builds question->view |
438 | 438 | */ |
439 | | - function parseQuestionBody( qp_AbstractQuestion $question ) { |
| 439 | + function parseQuestionBody( qp_StubQuestion $question ) { |
440 | 440 | if ( $question->getState() == 'error' ) { |
441 | 441 | # error occured during the previously performed header parsing, do not process further |
442 | 442 | $question->view->addHeaderError(); |
Index: trunk/extensions/QPoll/ctrl/qp_questionstats.php |
— | — | @@ -4,7 +4,8 @@ |
5 | 5 | die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
6 | 6 | } |
7 | 7 | |
8 | | -/* parsing, checking ans visualisation of Question in statistical display mode (UI input/output) |
| 8 | +/** |
| 9 | + * Parsing, checking and visualisation of Question in statistical display mode (UI output) |
9 | 10 | */ |
10 | 11 | class qp_QuestionStats extends qp_AbstractQuestion { |
11 | 12 | |
— | — | @@ -14,7 +15,7 @@ |
15 | 16 | * @param $poll an instance of question's parent controller |
16 | 17 | * @param $view an instance of question view "linked" to this question |
17 | 18 | * @param $type type of question (taken from DB) |
18 | | - * @param $questionId the identifier of the question used to gernerate input names |
| 19 | + * @param $questionId the identifier of the question used to generate input names |
19 | 20 | */ |
20 | 21 | function __construct( qp_PollStats $poll, qp_QuestionStatsView $view, $type, $questionId ) { |
21 | 22 | parent::__construct( $poll, $view, $questionId ); |
Index: trunk/extensions/QPoll/qp_user.php |
— | — | @@ -138,21 +138,30 @@ |
139 | 139 | |
140 | 140 | static $questionTypes = array( |
141 | 141 | 'mixed' => array( |
142 | | - 'className' => 'qp_MixedQuestion', |
| 142 | + 'ctrl' => 'qp_MixedQuestion', |
| 143 | + 'view' => 'qp_TabularQuestionView', |
143 | 144 | 'mType' => 'mixedChoice' |
144 | 145 | ), |
145 | 146 | 'unique()' => array( |
146 | | - 'className' => 'qp_TabularQuestion', |
| 147 | + 'ctrl' => 'qp_TabularQuestion', |
| 148 | + 'view' => 'qp_TabularQuestionView', |
147 | 149 | 'mType' => 'singleChoice', |
148 | 150 | 'mSubType' => 'unique' |
149 | 151 | ), |
150 | 152 | '()' => array( |
151 | | - 'className' => 'qp_TabularQuestion', |
| 153 | + 'ctrl' => 'qp_TabularQuestion', |
| 154 | + 'view' => 'qp_TabularQuestionView', |
152 | 155 | 'mType' => 'singleChoice' |
153 | 156 | ), |
154 | 157 | '[]' => array( |
155 | | - 'className' => 'qp_TabularQuestion', |
| 158 | + 'ctrl' => 'qp_TabularQuestion', |
| 159 | + 'view' => 'qp_TabularQuestionView', |
156 | 160 | 'mType' => 'multipleChoice' |
| 161 | + ), |
| 162 | + 'text' => array( |
| 163 | + 'ctrl' => 'qp_TextQuestion', |
| 164 | + 'view' => 'qp_TextQuestionView', |
| 165 | + 'mType' => 'textQuestion' |
157 | 166 | ) |
158 | 167 | ); |
159 | 168 | |
— | — | @@ -276,6 +285,7 @@ |
277 | 286 | 'ctrl/qp_stubquestion.php' => 'qp_StubQuestion', |
278 | 287 | 'ctrl/qp_tabularquestion.php' => 'qp_TabularQuestion', |
279 | 288 | 'ctrl/qp_mixedquestion.php' => 'qp_MixedQuestion', |
| 289 | + 'ctrl/qp_textquestion.php' => 'qp_TextQuestion', |
280 | 290 | 'ctrl/qp_questionstats.php' => 'qp_QuestionStats', |
281 | 291 | |
282 | 292 | ## views are derived from single generic class |
— | — | @@ -283,15 +293,22 @@ |
284 | 294 | # generic |
285 | 295 | 'view/qp_abstractview.php' => 'qp_AbstractView', |
286 | 296 | # questions |
287 | | - 'view/qp_questionview.php' => 'qp_QuestionView', |
| 297 | + 'view/qp_stubquestionview.php' => 'qp_StubQuestionView', |
| 298 | + 'view/qp_tabularquestionview.php' => 'qp_TabularQuestionView', |
| 299 | + 'view/qp_textquestionview.php' => 'qp_TextQuestionViewTokens', |
| 300 | + 'view/qp_textquestionview.php' => 'qp_TextQuestionView', |
288 | 301 | 'view/qp_questionstatsview.php' => 'qp_QuestionStatsView', |
289 | 302 | # polls |
290 | 303 | 'view/qp_pollview.php' => 'qp_PollView', |
291 | 304 | 'view/qp_pollstatsview.php' => 'qp_PollStatsView', |
292 | 305 | |
293 | | - # storage |
294 | | - 'qp_pollstore.php' => array( 'qp_QuestionData', 'qp_InterpResult', 'qp_PollStore' ), |
| 306 | + # poll storage |
| 307 | + 'qp_pollstore.php' => array( 'qp_InterpResult', 'qp_PollStore' ), |
295 | 308 | |
| 309 | + # question storage and page result question views |
| 310 | + # (combined question storage & view) |
| 311 | + 'qp_questiondata.php' => array( 'qp_QuestionData', 'qp_TextQuestionData' ), |
| 312 | + |
296 | 313 | # results page |
297 | 314 | 'qp_results.php' => array( 'qp_SpecialPage', 'qp_QueryPage', 'PollResults' ), |
298 | 315 | |
— | — | @@ -565,7 +582,7 @@ |
566 | 583 | $out[0][] = array( '__tag' => 'div', 'class' => 'script_view', qp_Setup::specialchars( $input ) . "\n" ); |
567 | 584 | $markercount = count( self::$markerList ); |
568 | 585 | $marker = "!qpoll-script-view{$markercount}-qpoll!"; |
569 | | - self::$markerList[$markercount] = qp_Renderer::renderHTMLobject( $out ); |
| 586 | + self::$markerList[$markercount] = qp_Renderer::renderTagArray( $out ); |
570 | 587 | return $marker; |
571 | 588 | } |
572 | 589 | |
— | — | @@ -601,10 +618,26 @@ |
602 | 619 | |
603 | 620 | /* renders output data */ |
604 | 621 | class qp_Renderer { |
605 | | - # the stucture of $tag is like this: |
606 | | - # array( "__tag"=>"td", "class"=>"myclass", 0=>"text before li", 1=>array( "__tag"=>"li", 0=>"text inside li" ), 2=>"text after li" ) |
607 | | - # both tagged and tagless lists are supported |
608 | | - static function renderHTMLobject( &$tag ) { |
| 622 | + |
| 623 | + static function tagError( $msg, &$tag ) { |
| 624 | + ob_start(); |
| 625 | + var_dump( $tag ); |
| 626 | + $tagdump = ob_get_contents(); |
| 627 | + ob_end_clean(); |
| 628 | + return "<u>invalid argument: " . qp_Setup::specialchars( $msg ) . "</u> <pre>{$tagdump}</pre>"; |
| 629 | + } |
| 630 | + |
| 631 | + /** |
| 632 | + * Renders nested tag array into string |
| 633 | + * @param $tag multidimensional array of xml/html tags |
| 634 | + * @return string representation of xml/html |
| 635 | + * |
| 636 | + * the stucture of $tag is like this: |
| 637 | + * array( "__tag"=>"td", "class"=>"myclass", 0=>"text before li", 1=>array( "__tag"=>"li", 0=>"text inside li" ), 2=>"text after li" ) |
| 638 | + * |
| 639 | + * both tagged and tagless lists are supported |
| 640 | + */ |
| 641 | + static function renderTagArray( &$tag ) { |
609 | 642 | $tag_open = ""; |
610 | 643 | $tag_close = ""; |
611 | 644 | $tag_val = null; |
— | — | @@ -612,20 +645,23 @@ |
613 | 646 | ksort( $tag ); |
614 | 647 | if ( array_key_exists( '__tag', $tag ) ) { |
615 | 648 | # list inside of tag |
616 | | - $tag_open .= "<" . $tag[ '__tag' ]; |
| 649 | + $tag_open .= "<" . $tag['__tag']; |
617 | 650 | foreach ( $tag as $attr_key => &$attr_val ) { |
618 | 651 | if ( is_int( $attr_key ) ) { |
619 | 652 | if ( $tag_val === null ) |
620 | 653 | $tag_val = ""; |
621 | 654 | if ( is_array( $attr_val ) ) { |
622 | 655 | # recursive tags |
623 | | - $tag_val .= self::renderHTMLobject( $attr_val ); |
| 656 | + $tag_val .= self::renderTagArray( $attr_val ); |
624 | 657 | } else { |
625 | 658 | # text |
626 | 659 | $tag_val .= $attr_val; |
627 | 660 | } |
628 | 661 | } else { |
629 | 662 | # string keys are for tag attributes |
| 663 | + if ( is_array( $attr_val ) || is_object( $attr_val ) || is_null( $attr_val ) ) { |
| 664 | + return self::tagError( "tagged list attribute key '{$attr_key}' should have scalar value", $tag ); |
| 665 | + } |
630 | 666 | if ( substr( $attr_key, 0, 2 ) != "__" ) { |
631 | 667 | # include only non-reserved attributes |
632 | 668 | $tag_open .= " $attr_key=\"" . $attr_val . "\""; |
— | — | @@ -648,17 +684,13 @@ |
649 | 685 | if ( is_int( $attr_key ) ) { |
650 | 686 | if ( is_array( $attr_val ) ) { |
651 | 687 | # recursive tags |
652 | | - $tag_val .= self::renderHTMLobject( $attr_val ); |
| 688 | + $tag_val .= self::renderTagArray( $attr_val ); |
653 | 689 | } else { |
654 | 690 | # text |
655 | 691 | $tag_val .= $attr_val; |
656 | 692 | } |
657 | 693 | } else { |
658 | | - ob_start(); |
659 | | - var_dump( $tag ); |
660 | | - $tagdump = ob_get_contents(); |
661 | | - ob_end_clean(); |
662 | | - $tag_val = "invalid argument: tagless list cannot have tag attribute values in key=$attr_key, $tagdump"; |
| 694 | + $tag_val = self::tagError( "tagless list cannot have tag attribute values in key=$attr_key", $tag ); |
663 | 695 | } |
664 | 696 | } |
665 | 697 | } |
— | — | @@ -670,7 +702,7 @@ |
671 | 703 | } |
672 | 704 | |
673 | 705 | /** |
674 | | - * add one or more of CSS class names to tag class attribute |
| 706 | + * add one or more CSS class name to tag class attribute |
675 | 707 | */ |
676 | 708 | static function addClass( &$tag, $className ) { |
677 | 709 | if ( !isset( $tag['class'] ) ) { |
— | — | @@ -682,9 +714,15 @@ |
683 | 715 | } |
684 | 716 | } |
685 | 717 | |
686 | | - # creates one "htmlobject" row of the table |
687 | | - # elements of $row can be either a string/number value of cell or an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag ) |
688 | | - # attribute maps can be like this: ("name"=>0, "count"=>colspan" ) |
| 718 | + /** |
| 719 | + * Creates one tagarray row of the table |
| 720 | + * @param $row a string/number value of cell or |
| 721 | + * an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag ) |
| 722 | + * @param $rowattrs array key val of new xml attributes to add to every destination cell |
| 723 | + * @param $attribute maps array with mapping of source cell xml attributes to |
| 724 | + * destination cell xml attributes ("name"=>0, "count"=>colspan" ) |
| 725 | + * @return array of destination cells |
| 726 | + */ |
689 | 727 | static function newRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) { |
690 | 728 | $result = ""; |
691 | 729 | if ( count( $row ) > 0 ) { |
— | — | @@ -692,8 +730,8 @@ |
693 | 731 | if ( !is_array( $cell ) ) { |
694 | 732 | $cell = array( 0 => $cell ); |
695 | 733 | } |
696 | | - $cell[ '__tag' ] = $celltag; |
697 | | - $cell[ '__end' ] = "\n"; |
| 734 | + $cell['__tag'] = $celltag; |
| 735 | + $cell['__end'] = "\n"; |
698 | 736 | if ( is_array( $attribute_maps ) ) { |
699 | 737 | # converts ("count"=>3) to ("colspan"=>3) in table headers - don't use frequently |
700 | 738 | foreach ( $attribute_maps as $key => $val ) { |
— | — | @@ -714,12 +752,18 @@ |
715 | 753 | return $result; |
716 | 754 | } |
717 | 755 | |
718 | | - # add row to the table |
| 756 | + /** |
| 757 | + * Add row to the table |
| 758 | + * todo: document |
| 759 | + */ |
719 | 760 | static function addRow( &$table, $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) { |
720 | 761 | $table[] = self::newRow( $row, $rowattrs, $celltag, $attribute_maps ); |
721 | 762 | } |
722 | 763 | |
723 | | - # add column to the table |
| 764 | + /** |
| 765 | + * Add column to the table |
| 766 | + * todo: document |
| 767 | + */ |
724 | 768 | static function addColumn( &$table, $column, $rowattrs = "", $celltag = "td", $attribute_maps = null ) { |
725 | 769 | if ( count( $column ) > 0 ) { |
726 | 770 | $row = 0; |
— | — | @@ -760,11 +804,13 @@ |
761 | 805 | static function displayRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) { |
762 | 806 | # temporary var $tagsrow used to avoid warning in E_STRICT mode |
763 | 807 | $tagsrow = self::newRow( $row, $rowattrs, $celltag, $attribute_maps ); |
764 | | - return self::renderHTMLobject( $tagsrow ); |
| 808 | + return self::renderTagArray( $tagsrow ); |
765 | 809 | } |
766 | 810 | |
767 | | - // use newRow() or addColumn() to add resulting row/column to the table |
768 | | - // if you want to use the resulting row with renderHTMLobject(), don't forget to apply attrs=array('__tag'=>'td') |
| 811 | + /** |
| 812 | + * use newRow() or addColumn() to add resulting row/column to the table |
| 813 | + * if you want to use the resulting row with renderTagArray(), don't forget to apply attrs=array('__tag'=>'td') |
| 814 | + */ |
769 | 815 | static function applyAttrsToRow( &$row, $attrs ) { |
770 | 816 | if ( is_array( $attrs ) && count( $attrs > 0 ) ) { |
771 | 817 | foreach ( $row as &$cell ) { |
Index: trunk/extensions/QPoll/qp_pollstore.php |
— | — | @@ -4,110 +4,6 @@ |
5 | 5 | die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
6 | 6 | } |
7 | 7 | |
8 | | -/* poll's single question data object RAM storage |
9 | | - * ( instances usually have short name qdata or quesdata ) |
10 | | - */ |
11 | | -class qp_QuestionData { |
12 | | - |
13 | | - // DB index (with current scheme is non-unique) |
14 | | - var $question_id = null; |
15 | | - // common properties |
16 | | - var $type; |
17 | | - var $CommonQuestion; |
18 | | - var $Categories; |
19 | | - var $CategorySpans; |
20 | | - var $ProposalText; |
21 | | - var $ProposalCategoryId; |
22 | | - var $ProposalCategoryText; |
23 | | - var $alreadyVoted = false; // whether the selected user already voted this question ? |
24 | | - // statistics storage |
25 | | - var $Votes = null; |
26 | | - var $Percents = null; |
27 | | - |
28 | | - function __construct( $argv ) { |
29 | | - if ( array_key_exists( 'from', $argv ) ) { |
30 | | - switch ( $argv[ 'from' ] ) { |
31 | | - case 'postdata' : |
32 | | - $this->type = $argv[ 'type' ]; |
33 | | - $this->CommonQuestion = $argv[ 'common_question' ]; |
34 | | - $this->Categories = $argv[ 'categories' ]; |
35 | | - $this->CategorySpans = $argv[ 'category_spans' ]; |
36 | | - $this->ProposalText = $argv[ 'proposal_text' ]; |
37 | | - $this->ProposalCategoryId = $argv[ 'proposal_category_id' ]; |
38 | | - $this->ProposalCategoryText = $argv[ 'proposal_category_text' ]; |
39 | | - break; |
40 | | - case 'qid' : |
41 | | - $this->question_id = $argv[ 'qid' ]; |
42 | | - $this->type = $argv[ 'type' ]; |
43 | | - $this->CommonQuestion = $argv[ 'common_question' ]; |
44 | | - $this->Categories = Array(); |
45 | | - $this->CategorySpans = Array(); |
46 | | - $this->ProposalText = Array(); |
47 | | - $this->ProposalCategoryId = Array(); |
48 | | - $this->ProposalCategoryText = Array(); |
49 | | - break; |
50 | | - } |
51 | | - } |
52 | | - } |
53 | | - |
54 | | - // integrate spans into categories |
55 | | - function packSpans() { |
56 | | - if ( count( $this->CategorySpans ) > 0 ) { |
57 | | - foreach ( $this->Categories as &$Cat ) { |
58 | | - if ( array_key_exists( 'spanId', $Cat ) ) { |
59 | | - $Cat['name'] = $this->CategorySpans[ $Cat['spanId'] ]['name'] . "\n" . $Cat['name']; |
60 | | - unset( $Cat['spanId'] ); |
61 | | - } |
62 | | - } |
63 | | - unset( $this->CategorySpans ); |
64 | | - $this->CategorySpans = Array(); |
65 | | - } |
66 | | - } |
67 | | - |
68 | | - // restore spans from categories |
69 | | - function restoreSpans() { |
70 | | - if ( count( $this->CategorySpans ) == 0 ) { |
71 | | - $prevSpanName = ''; |
72 | | - $spanId = -1; |
73 | | - foreach ( $this->Categories as &$Cat ) { |
74 | | - $a = explode( "\n", $Cat['name'] ); |
75 | | - if ( count( $a ) > 1 ) { |
76 | | - if ( $prevSpanName != $a[0] ) { |
77 | | - $spanId++; |
78 | | - $prevSpanName = $a[0]; |
79 | | - $this->CategorySpans[ $spanId ]['count'] = 0; |
80 | | - } |
81 | | - $Cat['name'] = $a[1]; |
82 | | - $Cat['spanId'] = $spanId; |
83 | | - $this->CategorySpans[ $spanId ]['name'] = $a[0]; |
84 | | - $this->CategorySpans[ $spanId ]['count']++; |
85 | | - } else { |
86 | | - $prevSpanName = ''; |
87 | | - } |
88 | | - } |
89 | | - } |
90 | | - } |
91 | | - |
92 | | - // check whether the previousely stored poll header is compatible with the one defined on the page |
93 | | - // used to reject previous vote in case the header is incompatble |
94 | | - function isCompatible( &$question ) { |
95 | | - if ( $question->mType != $this->type ) { |
96 | | - return false; |
97 | | - } |
98 | | - if ( count( $question->mCategorySpans ) != count( $this->CategorySpans ) ) { |
99 | | - return false; |
100 | | - } |
101 | | - foreach ( $question->mCategorySpans as $spanidx => &$span ) { |
102 | | - if ( !isset( $this->CategorySpans[ $spanidx ] ) || |
103 | | - $span[ "count" ] != $this->CategorySpans[ $spanidx ][ "count" ] ) { |
104 | | - return false; |
105 | | - } |
106 | | - } |
107 | | - return true; |
108 | | - } |
109 | | - |
110 | | -} /* end of qp_QuestionData class */ |
111 | | - |
112 | 8 | /** |
113 | 9 | * An interpretation result of user answer to the quiz |
114 | 10 | */ |
— | — | @@ -356,6 +252,23 @@ |
357 | 253 | } |
358 | 254 | } |
359 | 255 | |
| 256 | + /** |
| 257 | + * qdata instantiator |
| 258 | + * Please use it instead of qdata constructors |
| 259 | + */ |
| 260 | + static function newQuestionData( $argv ) { |
| 261 | + switch ( $argv['type'] ) { |
| 262 | + case 'textQuestion' : |
| 263 | + return new qp_TextQuestionData( $argv ); |
| 264 | + case 'singleChoice' : |
| 265 | + case 'multipleChoice' : |
| 266 | + case 'mixedChoice' : |
| 267 | + return new qp_QuestionData( $argv ); |
| 268 | + default : |
| 269 | + throw new MWException( 'Unknown type of question ' . qp_Setup::specialchars( $argv['type'] ) . ' in ' . __METHOD__ ); |
| 270 | + } |
| 271 | + } |
| 272 | + |
360 | 273 | function getPollId() { |
361 | 274 | return $this->mPollId; |
362 | 275 | } |
— | — | @@ -427,7 +340,7 @@ |
428 | 341 | $row->type = $typeFromVer0_5[$row->type]; |
429 | 342 | } |
430 | 343 | # create a qp_QuestionData object from DB fields |
431 | | - $this->Questions[ $question_id ] = new qp_QuestionData( array( |
| 344 | + $this->Questions[ $question_id ] = self::newQuestionData( array( |
432 | 345 | 'from' => 'qid', |
433 | 346 | 'qid' => $question_id, |
434 | 347 | 'type' => $row->type, |
Index: trunk/extensions/QPoll/view/qp_questionview.php |
— | — | @@ -1,547 +0,0 @@ |
2 | | -<?php |
3 | | -/** |
4 | | - * ***** BEGIN LICENSE BLOCK ***** |
5 | | - * This file is part of QPoll. |
6 | | - * Uses parts of code from Quiz extension (c) 2007 Louis-Rémi BABE. All rights reserved. |
7 | | - * |
8 | | - * QPoll is free software; you can redistribute it and/or modify |
9 | | - * it under the terms of the GNU General Public License as published by |
10 | | - * the Free Software Foundation; either version 2 of the License, or |
11 | | - * (at your option) any later version. |
12 | | - * |
13 | | - * QPoll is distributed in the hope that it will be useful, |
14 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | - * GNU General Public License for more details. |
17 | | - * |
18 | | - * You should have received a copy of the GNU General Public License |
19 | | - * along with QPoll; if not, write to the Free Software |
20 | | - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | | - * |
22 | | - * ***** END LICENSE BLOCK ***** |
23 | | - * |
24 | | - * QPoll is a poll tool for MediaWiki. |
25 | | - * |
26 | | - * To activate this extension : |
27 | | - * * Create a new directory named QPoll into the directory "extensions" of MediaWiki. |
28 | | - * * Place the files from the extension archive there. |
29 | | - * * Add this line at the end of your LocalSettings.php file : |
30 | | - * require_once "$IP/extensions/QPoll/qp_user.php"; |
31 | | - * |
32 | | - * @version 0.8.0a |
33 | | - * @link http://www.mediawiki.org/wiki/Extension:QPoll |
34 | | - * @author QuestPC <questpc@rambler.ru> |
35 | | - */ |
36 | | - |
37 | | -if ( !defined( 'MEDIAWIKI' ) ) { |
38 | | - die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
39 | | -} |
40 | | - |
41 | | -/** |
42 | | - * Stores question proposals views (see qp_qestion.php) and |
43 | | - * allows to modify these for quizes results at the later stage (see qp_poll.php) |
44 | | - * todo: transfer view logic completely from controllers |
45 | | - */ |
46 | | - |
47 | | -class qp_QuestionView extends qp_AbstractView { |
48 | | - |
49 | | - # error message which occured during the question header parsing that will be output later at rendering stage |
50 | | - var $headerErrorMessage = 'Unknown error'; |
51 | | - # display layout |
52 | | - var $proposalsFirst = false; |
53 | | - var $transposed = false; |
54 | | - var $spanType = 'colspan'; |
55 | | - var $categoriesStyle = ''; |
56 | | - var $signClass = ''; |
57 | | - var $proposalTextStyle = 'padding-left: 10px;'; |
58 | | - var $textInputStyle = ''; |
59 | | - |
60 | | - # whether to show the current stats to the users |
61 | | - # the following values are defined: |
62 | | - # false - use value of pollShowResults, Array(0) - suppress, Array(1) - percents, Array(2) - bars |
63 | | - var $showResults = false; |
64 | | - var $pollShowResults; |
65 | | - |
66 | | - # begin of proposalView |
67 | | - # these vars (and perhaps some more) should be part of separate proposalView, |
68 | | - # todo: in the future create separate proposal view with $row and $text ? |
69 | | - var $rawClass; |
70 | | - var $spanState; |
71 | | - # end of proposalView |
72 | | - |
73 | | - var $hview = array(); |
74 | | - # proposal views (indexed, sortable rows) |
75 | | - var $pview = array(); |
76 | | - |
77 | | - /** |
78 | | - * @param $parser |
79 | | - * @param $frame |
80 | | - * @param $showResults poll's showResults (may be overriden in the question) |
81 | | - */ |
82 | | - function __construct( &$parser, &$frame, $showResults ) { |
83 | | - parent::__construct( $parser, $frame ); |
84 | | - $this->pollShowResults = $showResults; |
85 | | - } |
86 | | - |
87 | | - /** |
88 | | - * Called for every proposal of the question |
89 | | - * todo: actually should be a member of separate small proposal view class |
90 | | - */ |
91 | | - function initProposalView() { |
92 | | - $this->rawClass = 'proposal'; |
93 | | - # stdClass instance used to draw borders around table cells via css |
94 | | - $this->spanState = (object) array( 'id' => 0, 'prevId' => -1, 'wasChecked' => true, 'isDrawing' => false, 'cellsLeft' => 0, 'className' => 'sign' ); |
95 | | - } |
96 | | - |
97 | | - static function newFromBaseView( $baseView ) { |
98 | | - return new qp_QuestionView( $baseView->parser, $baseView->ppframe, $baseView->showResults ); |
99 | | - } |
100 | | - |
101 | | - function isCompatibleController( $ctrl ) { |
102 | | - return method_exists( $ctrl, 'parseBody' ); |
103 | | - } |
104 | | - |
105 | | - function setLayout( $layout, $textwidth ) { |
106 | | - if ( count( $layout ) > 0 ) { |
107 | | - $this->transposed = strpos( $layout[1], 'transpose' ) !== false; |
108 | | - $this->proposalsFirst = strpos( $layout[1], 'proposals' ) !== false; |
109 | | - } |
110 | | - # setup question layout parameters |
111 | | - if ( $this->transposed ) { |
112 | | - $this->spanType = 'rowspan'; |
113 | | - $this->categoriesStyle = 'text-align:left; vertical-align:middle; '; |
114 | | - $this->signClass = array( 'first' => 'signt', 'middle' => 'signm', 'last' => 'signb' ); |
115 | | - $this->proposalTextStyle = 'text-align:center; padding-left: 5px; padding-right: 5px; '; |
116 | | - $this->proposalTextStyle .= ( $this->proposalsFirst ) ? ' vertical-align: bottom;' : 'vertical-align:top;'; |
117 | | - } else { |
118 | | - $this->spanType = 'colspan'; |
119 | | - $this->categoriesStyle = ''; |
120 | | - $this->signClass = array( 'first' => 'signl', 'middle' => 'signc', 'last' => 'signr' ); |
121 | | - $this->proposalTextStyle = 'vertical-align:middle; '; |
122 | | - $this->proposalTextStyle .= ( $this->proposalsFirst ) ? 'padding-right: 10px;' : 'padding-left: 10px;'; |
123 | | - } |
124 | | - if ( count( $textwidth ) > 0 ) { |
125 | | - $textwidth = intval( $textwidth[1] ); |
126 | | - if ( $textwidth > 0 ) { |
127 | | - $this->textInputStyle = 'width:' . $textwidth . 'em;'; |
128 | | - } |
129 | | - } |
130 | | - } |
131 | | - |
132 | | - function setShowResults( $showresults ) { |
133 | | - # setup question's showresults when global showresults != 0 |
134 | | - if ( qp_Setup::$global_showresults != 0 && count( $showresults ) > 0 ) { |
135 | | - # use the value from the question |
136 | | - $this->showResults = qp_AbstractPoll::parseShowResults( $showresults[1] ); |
137 | | - # apply undefined attributes from the poll's showresults definition |
138 | | - foreach ( $this->pollShowResults as $attr => $val ) { |
139 | | - if ( $attr != 'type' && !isset( $this->showResults[$attr] ) ) { |
140 | | - $this->showResults[$attr] = $val; |
141 | | - } |
142 | | - } |
143 | | - } else { |
144 | | - # use the value "inherited" from the poll |
145 | | - $this->showResults = $this->pollShowResults; |
146 | | - } |
147 | | - # initialize cell template for the selected showresults |
148 | | - # this method call can be moved to $ctrl->parseBody() in the future, |
149 | | - # if needed to setup templates depending on question type |
150 | | - # right now, cell templates depend only on input type and showresults type |
151 | | - if ( $this->showResults['type'] != 0 ) { |
152 | | - $this->{ 'cellTemplate' . $this->showResults['type'] }(); |
153 | | - } |
154 | | - } |
155 | | - |
156 | | - /** |
157 | | - * Builds tagarray of categories |
158 | | - * @param $categories "raw" categories |
159 | | - */ |
160 | | - function buildCategoriesRow( $categories ) { |
161 | | - $row = array(); |
162 | | - if ( $this->proposalsFirst ) { |
163 | | - // add empty <th> at the begin of row to "compensate" proposal text |
164 | | - $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
165 | | - } |
166 | | - foreach ( $categories as $cat ) { |
167 | | - $row[] = $this->rtp( $cat['name'] ); |
168 | | - } |
169 | | - if ( !$this->proposalsFirst ) { |
170 | | - // add empty <th> at the end of row to "compensate" proposal text |
171 | | - $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
172 | | - } |
173 | | - return $row; |
174 | | - } |
175 | | - |
176 | | - /** |
177 | | - * Builds tagarray of category spans |
178 | | - * @param $categorySpans "raw" spans |
179 | | - */ |
180 | | - function buildSpansRow( $categorySpans ) { |
181 | | - $row = array(); |
182 | | - if ( $this->proposalsFirst ) { |
183 | | - // add empty <th> at the begin of row to "compensate" proposal text |
184 | | - $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
185 | | - } |
186 | | - foreach ( $categorySpans as &$span ) { |
187 | | - $row[] = array( "count" => $span['count'], 0 => $this->rtp( $span['name'] ) ); |
188 | | - } |
189 | | - if ( !$this->proposalsFirst ) { |
190 | | - // add empty <th> at the end of row to "compensate" proposal text |
191 | | - $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
192 | | - } |
193 | | - return $row; |
194 | | - } |
195 | | - |
196 | | - /** |
197 | | - * @param $tagarray array / string tags to add to the question's header |
198 | | - */ |
199 | | - function addHeader( $tagarray ) { |
200 | | - # todo: replace all the occurencies of 'htmlobject' to 'tagarray' |
201 | | - $this->hview[] = $tagarray; |
202 | | - } |
203 | | - |
204 | | - function addHeaderError() { |
205 | | - $this->hview[] = array( '__tag' => 'div', 'class' => 'proposalerror', $this->headerErrorMessage ); |
206 | | - } |
207 | | - |
208 | | - /** |
209 | | - * Adds table header row to question's view |
210 | | - * @param $row tagarray representation of row |
211 | | - * @param $className CSS class name of row |
212 | | - * @param $attribute_maps translation of source attributes into html attributes (see qp_Renderer class) |
213 | | - */ |
214 | | - function addHeaderRow( $row, $className, $attribute_maps = null ) { |
215 | | - $this->hview[] = (object) array( |
216 | | - 'row' => $row, |
217 | | - 'className' => $className, |
218 | | - 'attribute_maps' => $attribute_maps |
219 | | - ); |
220 | | - } |
221 | | - |
222 | | - /** |
223 | | - * Adds category spans row to question's view |
224 | | - */ |
225 | | - function addSpanRow( $row ) { |
226 | | - # apply categoriesStyle |
227 | | - if ( $this->categoriesStyle != '' ) { |
228 | | - qp_Renderer::applyAttrsToRow( $row, array( 'style' => $this->categoriesStyle ) ); |
229 | | - } |
230 | | - $this->hview[] = (object) array( |
231 | | - 'row' => $row, |
232 | | - 'className' => 'spans', |
233 | | - 'attribute_maps' => array( 'count' => $this->spanType, 'name' => 0 ) |
234 | | - ); |
235 | | - } |
236 | | - |
237 | | - /** |
238 | | - * Adds categories row to question's view |
239 | | - */ |
240 | | - function addCategoryRow( $row ) { |
241 | | - # apply categoriesStyle |
242 | | - if ( $this->categoriesStyle != '' ) { |
243 | | - qp_Renderer::applyAttrsToRow( $row, array( 'style' => $this->categoriesStyle ) ); |
244 | | - } |
245 | | - $this->hview[] = (object) array( |
246 | | - 'row' => $row, |
247 | | - 'className' => 'categories', |
248 | | - 'attribute_maps' => array( 'name' => 0 ) |
249 | | - ); |
250 | | - } |
251 | | - |
252 | | - /** |
253 | | - * @param $proposalId int index of proposal |
254 | | - * @param $text string proposal text |
255 | | - * @param $row array representation of html table row associated with current proposal |
256 | | - */ |
257 | | - function addProposal( $proposalId, $text, $row ) { |
258 | | - $this->pview[$proposalId] = (object) array( |
259 | | - 'text' => $text, |
260 | | - 'row' => $row, |
261 | | - 'className' => $this->rawClass |
262 | | - ); |
263 | | - } |
264 | | - |
265 | | - /** |
266 | | - * Outputs question body parser error/warning message; also set new controller state |
267 | | - * @param $msg - text of message |
268 | | - * @param $state - set new question controller state |
269 | | - * note that the 'error' state cannot be changed and '' state cannot be set |
270 | | - * @param $rawClass - string set rawClass value or false (do not set) |
271 | | - */ |
272 | | - function bodyErrorMessage( $msg, $state, $rawClass = 'proposalerror' ) { |
273 | | - $prev_state = $this->ctrl->getState(); |
274 | | - # do not clear previous errors (do not call setState() when $state == '') |
275 | | - if ( $state != '' ) { |
276 | | - $this->ctrl->setState( $state, $msg ); |
277 | | - } |
278 | | - # return the message only for the first error occured |
279 | | - # (this one has to be short, because title attribute is being used) |
280 | | - if ( is_string( $rawClass ) ) { |
281 | | - $this->rawClass = $rawClass; |
282 | | - } |
283 | | - # show only the first error, when the state is not clean (not '') |
284 | | - return ( $prev_state == '' ) ? '<span class="proposalerror" title="' . qp_Setup::specialchars( $msg ) . '">???</span> ' : ''; |
285 | | - } |
286 | | - |
287 | | - /** |
288 | | - * Draws borders via css in the question table according to |
289 | | - * controller's category spans (metacategories) |
290 | | - * todo: this function takes too much arguments; |
291 | | - * figure out how to split it into smaller parts |
292 | | - */ |
293 | | - function renderSpan( &$row, &$name, $value, $catId, $catDesc, &$text ) { |
294 | | - $spanState = &$this->spanState; |
295 | | - if ( array_key_exists( 'spanId', $catDesc ) ) { |
296 | | - $spanState->id = $catDesc[ 'spanId' ]; |
297 | | - $name .= 's' . $spanState->id; |
298 | | - # there can't be more category spans than the categories |
299 | | - if ( $this->ctrl->mCategorySpans[ $spanState->id ]['count'] > count( $this->ctrl->mCategories ) ) { |
300 | | - $text = $this->bodyErrorMessage( wfMsg( 'qp_error_too_many_spans' ), 'error' ) . $text; |
301 | | - $this->rawClass = 'proposalerror'; |
302 | | - } |
303 | | - if ( $spanState->prevId != $spanState->id ) { |
304 | | - # begin of new span |
305 | | - $spanState->wasChecked = false; |
306 | | - $spanState->prevId = $spanState->id; |
307 | | - $spanState->cellsLeft = $this->ctrl->mCategorySpans[ $spanState->id ]['count']; |
308 | | - if ( $spanState->cellsLeft < 2 ) { |
309 | | - $text = $this->bodyErrorMessage( wfMsg( 'qp_error_too_few_spans' ), 'error' ) . $text; |
310 | | - QP_Renderer::addClass( $row[ $catId ], 'error' ); |
311 | | - } |
312 | | - $spanState->isDrawing = $spanState->cellsLeft != 1 && $spanState->cellsLeft != count( $this->ctrl->mCategories ); |
313 | | - # hightlight only spans of count != 1 and count != count(categories) |
314 | | - if ( $spanState->isDrawing ) { |
315 | | - $spanState->className = $this->signClass[ 'first' ]; |
316 | | - } |
317 | | - } else { |
318 | | - $spanState->cellsLeft--; |
319 | | - if ( $spanState->isDrawing ) { |
320 | | - $spanState->className = ( $spanState->cellsLeft > 1 ) ? $this->signClass[ 'middle' ] : $this->signClass[ 'last' ]; |
321 | | - } |
322 | | - } |
323 | | - } |
324 | | - # todo: figure out how to split this part to separate function |
325 | | - # this part is unneeded for question stats controller, |
326 | | - # which should never have $this->ctrl->poll->mBeingCorrected === true |
327 | | - if ( $spanState->cellsLeft <= 1 ) { |
328 | | - # end of new span |
329 | | - if ( $this->ctrl->poll->mBeingCorrected && |
330 | | - !$spanState->wasChecked && |
331 | | - $this->ctrl->mRequest->getVal( $name ) != $value ) { |
332 | | - # the span (a part of proposal) was submitted but unanswered |
333 | | - $text = $this->bodyErrorMessage( wfMsg( 'qp_error_unanswered_span' ), 'NA' ) . $text; |
334 | | - # highlight current span to indicate an error |
335 | | - for ( $i = $catId, $j = $this->ctrl->mCategorySpans[ $spanState->id ]['count']; $j > 0; $i--, $j-- ) { |
336 | | - QP_Renderer::addClass( $row[$i], 'error' ); |
337 | | - } |
338 | | - $this->rawClass = 'proposalerror'; |
339 | | - } |
340 | | - } |
341 | | - } |
342 | | - |
343 | | - /** |
344 | | - * Render script-generated proposal errors, when available (quiz mode) |
345 | | - * Note: not being called in stats mode |
346 | | - */ |
347 | | - function renderInterpErrors() { |
348 | | - if ( ( $propErrors = $this->ctrl->getProposalsErrors() ) === false ) { |
349 | | - return; |
350 | | - } |
351 | | - foreach ( $this->pview as $prop_id => &$propview ) { |
352 | | - if ( isset( $propErrors[$prop_id] ) ) { |
353 | | - $msg = is_string( $propErrors[$prop_id] ) ? $propErrors[$prop_id] : wfMsg( 'qp_interpetation_wrong_answer' ); |
354 | | - $propview->text = $this->bodyErrorMessage( $msg, '', false ) . $propview->text; |
355 | | - } |
356 | | - } |
357 | | - } |
358 | | - |
359 | | - /** |
360 | | - * Renders question table with header and proposal views |
361 | | - */ |
362 | | - private function renderTable() { |
363 | | - $questionTable = array(); |
364 | | - # add header views to $questionTable |
365 | | - foreach ( $this->hview as $header ) { |
366 | | - $rowattrs = ''; |
367 | | - $attribute_maps = null; |
368 | | - if ( is_object( $header ) ) { |
369 | | - $row = &$header->row; |
370 | | - $rowattrs = array( 'class' => $header->className ); |
371 | | - $attribute_maps = &$header->attribute_maps; |
372 | | - } else { |
373 | | - $row = &$header; |
374 | | - } |
375 | | - if ( $this->transposed ) { |
376 | | - qp_Renderer::addColumn( $questionTable, $row, $rowattrs, 'th', $attribute_maps ); |
377 | | - } else { |
378 | | - qp_Renderer::addRow( $questionTable, $row, $rowattrs, 'th', $attribute_maps ); |
379 | | - } |
380 | | - } |
381 | | - # add proposal views to $questionTable |
382 | | - ksort( $this->pview ); |
383 | | - foreach ( $this->pview as $propview ) { |
384 | | - $row = &$propview->row; |
385 | | - $rowattrs = array( 'class' => $propview->className ); |
386 | | - $text = array( '__tag' => 'td', '__end' => "\n", 'class' => 'proposaltext', 'style' => $this->proposalTextStyle, 0 => $this->rtp( $propview->text ) ); |
387 | | - # insert proposal text to the beginning / end according to proposalsFirst property |
388 | | - if ( $this->proposalsFirst ) { |
389 | | - # first element is proposaltext |
390 | | - array_unshift( $row, $text ); |
391 | | - } else { |
392 | | - # last element is proposaltext |
393 | | - $row[] = $text; |
394 | | - } |
395 | | - if ( $this->transposed ) { |
396 | | - qp_Renderer::addColumn( $questionTable, $row, $rowattrs ); |
397 | | - } else { |
398 | | - qp_Renderer::addRow( $questionTable, $row, $rowattrs ); |
399 | | - } |
400 | | - } |
401 | | - return $questionTable; |
402 | | - } |
403 | | - |
404 | | - /** |
405 | | - * todo: unfortunately, rendering the question also conditionally modifies state of poll controller |
406 | | - * @modifies parent controller |
407 | | - */ |
408 | | - function renderQuestion() { |
409 | | - $output_table = array( '__tag' => 'table', '__end' => "\n", 'class' => 'object' ); |
410 | | - # Determine the side border color the question. |
411 | | - if ( $this->ctrl->getState() != '' ) { |
412 | | - if ( isset( $output_table['class'] ) ) { |
413 | | - $output_table['class'] .= ' error_mark'; |
414 | | - } else { |
415 | | - $output_table['class'] = 'error_mark'; |
416 | | - } |
417 | | - # set poll controller state according to question controller state |
418 | | - $this->ctrl->applyStateToParent(); |
419 | | - } |
420 | | - $output_table[] = array( '__tag' => 'tbody', '__end' => "\n", 0 => $this->renderTable() ); |
421 | | - $tags = array( '__tag' => 'div', '__end' => "\n", 'class' => 'question', |
422 | | - 0 => array( '__tag' => 'div', '__end' => "\n", 'class' => 'header', |
423 | | - 0 => array( '__tag' => 'span', 'class' => 'questionId', 0 => $this->ctrl->usedId ) |
424 | | - ), |
425 | | - 1 => array( '__tag' => 'div', 0 => $this->rtp( $this->ctrl->mCommonQuestion ) ) |
426 | | - ); |
427 | | - $tags[] = &$output_table; |
428 | | - return qp_Renderer::renderHTMLobject( $tags ); |
429 | | - } |
430 | | - |
431 | | - /*** cell templates ***/ |
432 | | - |
433 | | - function hasShowResults() { |
434 | | - return $this->showResults['type'] != 0 && |
435 | | - method_exists( $this, 'addShowResults' . $this->showResults['type'] ); |
436 | | - } |
437 | | - |
438 | | - function addShowResults( $inp, $proposalId, $catId ) { |
439 | | - return $this->{ 'addShowResults' . $this->showResults['type'] }( $inp, $proposalId, $catId ); |
440 | | - } |
441 | | - |
442 | | - # cell templates for the selected showresults |
443 | | - var $cellTemplate = Array(); |
444 | | - var $cellTemplateParam = Array( 'inp' => '', 'percents' => '', 'bar1style' => '', 'bar2style' => '' ); |
445 | | - |
446 | | - # setup a template for showresults=1 |
447 | | - # showresults=1 cellTemplate has only one variant |
448 | | - function cellTemplate1() { |
449 | | - $this->cellTemplate = |
450 | | - array( |
451 | | - 0 => array( '__tag' => 'div', 0 => &$this->cellTemplateParam['inp'] ), |
452 | | - 1 => array( '__tag' => 'div', 'class' => 'stats', 0 => &$this->cellTemplateParam['percents'] ) |
453 | | - ); |
454 | | - if ( isset( $this->showResults['color'] ) ) { |
455 | | - $this->cellTemplate[1]['style'] = 'color:' . $this->showResults['color'] . ';'; |
456 | | - } |
457 | | - if ( isset( $this->showResults['background'] ) ) { |
458 | | - $this->cellTemplate[1]['style'] .= 'background:' . $this->showResults['background'] . ';'; |
459 | | - } |
460 | | - } |
461 | | - |
462 | | - # transform input according to showresults=1 (numerical percents) |
463 | | - # *** warning! parameters should be passed only by value, not by reference *** |
464 | | - function addShowResults1( $inp, $proposalId, $catId ) { |
465 | | - $this->cellTemplateParam['inp'] = $inp; |
466 | | - $this->cellTemplateParam['percents'] = ' '; |
467 | | - if ( ( $percents = $this->ctrl->getPercents( $proposalId, $catId ) ) !== false ) { |
468 | | - # there is a stat in cell |
469 | | - $this->cellTemplateParam['percents'] = $percents . '%'; |
470 | | - # template has to be rendered immediately, because $this->cellTemplateParam[] are used as pointers and thus, |
471 | | - # will always be overwritten |
472 | | - return QP_Renderer::renderHTMLobject( $this->cellTemplate ); |
473 | | - } else { |
474 | | - return $inp; |
475 | | - } |
476 | | - } |
477 | | - |
478 | | - # setup a template for showresults=2 |
479 | | - function cellTemplate2() { |
480 | | - # statical styles |
481 | | - $percentstyle = ''; |
482 | | - if ( isset( $this->showResults['textcolor'] ) ) { |
483 | | - $percentstyle = 'color:' . $this->showResults['textcolor'] . ';'; |
484 | | - } |
485 | | - if ( isset( $this->showResults['textbackground'] ) ) { |
486 | | - $percentstyle .= 'background:' . $this->showResults['textbackground'] . ';'; |
487 | | - } |
488 | | - # html arrays used in templates below |
489 | | - $bar = array( '__tag' => 'div', 'class' => 'stats1', |
490 | | - 0 => array( '__tag' => 'div', 'class' => 'bar0', 0 => &$this->cellTemplateParam['inp'] ), |
491 | | - 1 => array( '__tag' => 'div', 'class' => 'bar1', 'style' => &$this->cellTemplateParam['bar1style'], 0 => ' ' ), |
492 | | - 2 => array( '__tag' => 'div', 'class' => 'bar2', 'style' => &$this->cellTemplateParam['bar2style'], 0 => ' ' ), |
493 | | - 3 => array( '__tag' => 'div', 'class' => 'bar0', 'style' => $percentstyle, 0 => &$this->cellTemplateParam['percents'] ) |
494 | | - ); |
495 | | - $bar2 = array( '__tag' => 'div', 'class' => 'stats1', |
496 | | - 0 => array( '__tag' => 'div', 'class' => 'bar0', 0 => ' ' ), |
497 | | - 1 => &$bar[1], |
498 | | - 2 => &$bar[2], |
499 | | - 3 => &$bar[3] |
500 | | - ); |
501 | | - # has two available templates ('bar','textinput') |
502 | | - $this->cellTemplate = array( |
503 | | - 'bar' => $bar, |
504 | | - 'textinput' => array( '__tag' => 'table', 'class' => 'stats', |
505 | | - 0 => array( '__tag' => 'tr', |
506 | | - 0 => array( '__tag' => 'td', 0 => &$this->cellTemplateParam['inp'] ), |
507 | | - ), |
508 | | - 1 => array( '__tag' => 'tr', |
509 | | - 0 => array( '__tag' => 'td', |
510 | | - 0 => $bar2 |
511 | | - ) |
512 | | - ) |
513 | | - ), |
514 | | - # the following entries are not real templates, but pre-calculated values of css attributes taken from showresults parameter |
515 | | - 'bar1showres' => '', |
516 | | - 'bar2showres' => '' |
517 | | - ); |
518 | | - # dynamical styles, width: in percents will be added during rendering in addShowResults |
519 | | - if ( isset( $this->showResults['color'] ) ) { |
520 | | - $this->cellTemplate['bar1showres'] .= 'background:' . $this->showResults['color'] . ';'; |
521 | | - } |
522 | | - if ( isset( $this->showResults['background'] ) ) { |
523 | | - $this->cellTemplate['bar2showres'] .= 'background:' . $this->showResults['background'] . ';'; |
524 | | - } |
525 | | - } |
526 | | - |
527 | | - # transform input according to showresults=2 (bars) |
528 | | - # *** warning! parameters should be passed only by value, not by reference *** |
529 | | - function addShowResults2( $inp, $proposalId, $catId ) { |
530 | | - $this->cellTemplateParam['inp'] = $inp; |
531 | | - $this->cellTemplateParam['percents'] = ' '; |
532 | | - if ( ( $percents = $this->ctrl->getPercents( $proposalId, $catId ) ) !== false ) { |
533 | | - # there is a stat in cell |
534 | | - $this->cellTemplateParam['percents'] = $percents . '%'; |
535 | | - $this->cellTemplateParam['bar1style'] = 'width:' . $percents . 'px;' . $this->cellTemplate[ 'bar1showres' ]; |
536 | | - $this->cellTemplateParam['bar2style'] = 'width:' . ( 100 - $percents ) . 'px;' . $this->cellTemplate[ 'bar2showres' ]; |
537 | | - if ( $inp['type'] == 'text' ) { |
538 | | - return qp_Renderer::renderHTMLobject( $this->cellTemplate['textinput'] ); |
539 | | - } else { |
540 | | - return qp_Renderer::renderHTMLobject( $this->cellTemplate['bar'] ); |
541 | | - } |
542 | | - } else { |
543 | | - return $inp; |
544 | | - } |
545 | | - } |
546 | | - /*** end of cell templates ***/ |
547 | | - |
548 | | -} /* end of qp_QuestionView class */ |
Index: trunk/extensions/QPoll/view/qp_tabularquestionview.php |
— | — | @@ -0,0 +1,446 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * ***** BEGIN LICENSE BLOCK ***** |
| 5 | + * This file is part of QPoll. |
| 6 | + * Uses parts of code from Quiz extension (c) 2007 Louis-Rémi BABE. All rights reserved. |
| 7 | + * |
| 8 | + * QPoll is free software; you can redistribute it and/or modify |
| 9 | + * it under the terms of the GNU General Public License as published by |
| 10 | + * the Free Software Foundation; either version 2 of the License, or |
| 11 | + * (at your option) any later version. |
| 12 | + * |
| 13 | + * QPoll is distributed in the hope that it will be useful, |
| 14 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | + * GNU General Public License for more details. |
| 17 | + * |
| 18 | + * You should have received a copy of the GNU General Public License |
| 19 | + * along with QPoll; if not, write to the Free Software |
| 20 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 21 | + * |
| 22 | + * ***** END LICENSE BLOCK ***** |
| 23 | + * |
| 24 | + * QPoll is a poll tool for MediaWiki. |
| 25 | + * |
| 26 | + * To activate this extension : |
| 27 | + * * Create a new directory named QPoll into the directory "extensions" of MediaWiki. |
| 28 | + * * Place the files from the extension archive there. |
| 29 | + * * Add this line at the end of your LocalSettings.php file : |
| 30 | + * require_once "$IP/extensions/QPoll/qp_user.php"; |
| 31 | + * |
| 32 | + * @version 0.8.0a |
| 33 | + * @link http://www.mediawiki.org/wiki/Extension:QPoll |
| 34 | + * @author QuestPC <questpc@rambler.ru> |
| 35 | + */ |
| 36 | + |
| 37 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 38 | + die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * Stores question proposals views (see qp_qestion.php) and |
| 43 | + * allows to modify these for quizes results at the later stage (see qp_poll.php) |
| 44 | + * todo: transfer view logic completely from controllers |
| 45 | + */ |
| 46 | + |
| 47 | +class qp_TabularQuestionView extends qp_StubQuestionView { |
| 48 | + |
| 49 | + # display layout |
| 50 | + var $proposalsFirst = false; |
| 51 | + var $transposed = false; |
| 52 | + var $spanType = 'colspan'; |
| 53 | + var $categoriesStyle = ''; |
| 54 | + var $signClass = ''; |
| 55 | + var $proposalTextStyle = 'padding-left: 10px;'; |
| 56 | + var $textInputStyle = ''; |
| 57 | + |
| 58 | + # whether to show the current stats to the users |
| 59 | + # the following values are defined: |
| 60 | + # false - use value of pollShowResults, Array(0) - suppress, Array(1) - percents, Array(2) - bars |
| 61 | + var $showResults = false; |
| 62 | + var $pollShowResults; |
| 63 | + |
| 64 | + # begin of proposalView |
| 65 | + # these vars (and perhaps some more) should be part of separate proposalView, |
| 66 | + # todo: in the future create separate proposal view with $row and $text ? |
| 67 | + var $rawClass; |
| 68 | + var $spanState; |
| 69 | + # end of proposalView |
| 70 | + |
| 71 | + /** |
| 72 | + * @param $parser |
| 73 | + * @param $frame |
| 74 | + * @param $showResults poll's showResults (may be overriden in the question) |
| 75 | + */ |
| 76 | + function __construct( &$parser, &$frame, $showResults ) { |
| 77 | + parent::__construct( $parser, $frame ); |
| 78 | + $this->pollShowResults = $showResults; |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * Called for every proposal of the question |
| 83 | + * todo: actually should be a member of separate small proposal view class |
| 84 | + */ |
| 85 | + function initProposalView() { |
| 86 | + $this->rawClass = 'proposal'; |
| 87 | + # stdClass instance used to draw borders around table cells via css |
| 88 | + $this->spanState = (object) array( 'id' => 0, 'prevId' => -1, 'wasChecked' => true, 'isDrawing' => false, 'cellsLeft' => 0, 'className' => 'sign' ); |
| 89 | + } |
| 90 | + |
| 91 | + static function newFromBaseView( $baseView ) { |
| 92 | + return new self( $baseView->parser, $baseView->ppframe, $baseView->showResults ); |
| 93 | + } |
| 94 | + |
| 95 | + function setLayout( $layout, $textwidth ) { |
| 96 | + if ( count( $layout ) > 0 ) { |
| 97 | + $this->transposed = strpos( $layout[1], 'transpose' ) !== false; |
| 98 | + $this->proposalsFirst = strpos( $layout[1], 'proposals' ) !== false; |
| 99 | + } |
| 100 | + # setup question layout parameters |
| 101 | + if ( $this->transposed ) { |
| 102 | + $this->spanType = 'rowspan'; |
| 103 | + $this->categoriesStyle = 'text-align:left; vertical-align:middle; '; |
| 104 | + $this->signClass = array( 'first' => 'signt', 'middle' => 'signm', 'last' => 'signb' ); |
| 105 | + $this->proposalTextStyle = 'text-align:center; padding-left: 5px; padding-right: 5px; '; |
| 106 | + $this->proposalTextStyle .= ( $this->proposalsFirst ) ? ' vertical-align: bottom;' : 'vertical-align:top;'; |
| 107 | + } else { |
| 108 | + $this->spanType = 'colspan'; |
| 109 | + $this->categoriesStyle = ''; |
| 110 | + $this->signClass = array( 'first' => 'signl', 'middle' => 'signc', 'last' => 'signr' ); |
| 111 | + $this->proposalTextStyle = 'vertical-align:middle; '; |
| 112 | + $this->proposalTextStyle .= ( $this->proposalsFirst ) ? 'padding-right: 10px;' : 'padding-left: 10px;'; |
| 113 | + } |
| 114 | + if ( count( $textwidth ) > 0 ) { |
| 115 | + $textwidth = intval( $textwidth[1] ); |
| 116 | + if ( $textwidth > 0 ) { |
| 117 | + $this->textInputStyle = 'width:' . $textwidth . 'em;'; |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + function setShowResults( $showresults ) { |
| 123 | + # setup question's showresults when global showresults != 0 |
| 124 | + if ( qp_Setup::$global_showresults != 0 && count( $showresults ) > 0 ) { |
| 125 | + # use the value from the question |
| 126 | + $this->showResults = qp_AbstractPoll::parseShowResults( $showresults[1] ); |
| 127 | + # apply undefined attributes from the poll's showresults definition |
| 128 | + foreach ( $this->pollShowResults as $attr => $val ) { |
| 129 | + if ( $attr != 'type' && !isset( $this->showResults[$attr] ) ) { |
| 130 | + $this->showResults[$attr] = $val; |
| 131 | + } |
| 132 | + } |
| 133 | + } else { |
| 134 | + # use the value "inherited" from the poll |
| 135 | + $this->showResults = $this->pollShowResults; |
| 136 | + } |
| 137 | + # initialize cell template for the selected showresults |
| 138 | + # this method call can be moved to $ctrl->parseBody() in the future, |
| 139 | + # if needed to setup templates depending on question type |
| 140 | + # right now, cell templates depend only on input type and showresults type |
| 141 | + if ( $this->showResults['type'] != 0 ) { |
| 142 | + $this->{ 'cellTemplate' . $this->showResults['type'] }(); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Builds tagarray of categories |
| 148 | + * @param $categories "raw" categories |
| 149 | + */ |
| 150 | + function buildCategoriesRow( $categories ) { |
| 151 | + $row = array(); |
| 152 | + if ( $this->proposalsFirst ) { |
| 153 | + // add empty <th> at the begin of row to "compensate" proposal text |
| 154 | + $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
| 155 | + } |
| 156 | + foreach ( $categories as $cat ) { |
| 157 | + $row[] = $this->rtp( $cat['name'] ); |
| 158 | + } |
| 159 | + if ( !$this->proposalsFirst ) { |
| 160 | + // add empty <th> at the end of row to "compensate" proposal text |
| 161 | + $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
| 162 | + } |
| 163 | + return $row; |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Builds tagarray of category spans |
| 168 | + * @param $categorySpans "raw" spans |
| 169 | + */ |
| 170 | + function buildSpansRow( $categorySpans ) { |
| 171 | + $row = array(); |
| 172 | + if ( $this->proposalsFirst ) { |
| 173 | + // add empty <th> at the begin of row to "compensate" proposal text |
| 174 | + $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
| 175 | + } |
| 176 | + foreach ( $categorySpans as &$span ) { |
| 177 | + $row[] = array( "count" => $span['count'], 0 => $this->rtp( $span['name'] ) ); |
| 178 | + } |
| 179 | + if ( !$this->proposalsFirst ) { |
| 180 | + // add empty <th> at the end of row to "compensate" proposal text |
| 181 | + $row[] = array( '__tag' => 'td', 0 => "", 'style' => 'border:none;', '__end' => "\n" ); |
| 182 | + } |
| 183 | + return $row; |
| 184 | + } |
| 185 | + |
| 186 | + /** |
| 187 | + * Adds category spans row to question's view |
| 188 | + */ |
| 189 | + function addSpanRow( $row ) { |
| 190 | + # apply categoriesStyle |
| 191 | + if ( $this->categoriesStyle != '' ) { |
| 192 | + qp_Renderer::applyAttrsToRow( $row, array( 'style' => $this->categoriesStyle ) ); |
| 193 | + } |
| 194 | + $this->hview[] = (object) array( |
| 195 | + 'row' => $row, |
| 196 | + 'className' => 'spans', |
| 197 | + 'attribute_maps' => array( 'count' => $this->spanType, 'name' => 0 ) |
| 198 | + ); |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Adds categories row to question's view |
| 203 | + */ |
| 204 | + function addCategoryRow( $row ) { |
| 205 | + # apply categoriesStyle |
| 206 | + if ( $this->categoriesStyle != '' ) { |
| 207 | + qp_Renderer::applyAttrsToRow( $row, array( 'style' => $this->categoriesStyle ) ); |
| 208 | + } |
| 209 | + $this->hview[] = (object) array( |
| 210 | + 'row' => $row, |
| 211 | + 'className' => 'categories', |
| 212 | + 'attribute_maps' => array( 'name' => 0 ) |
| 213 | + ); |
| 214 | + } |
| 215 | + |
| 216 | + /** |
| 217 | + * @param $proposalId int index of proposal |
| 218 | + * @param $text string proposal text |
| 219 | + * @param $row array representation of html table row associated with current proposal |
| 220 | + */ |
| 221 | + function addProposal( $proposalId, $text, $row ) { |
| 222 | + $this->pview[$proposalId] = (object) array( |
| 223 | + 'text' => $text, |
| 224 | + 'row' => $row, |
| 225 | + 'className' => $this->rawClass |
| 226 | + ); |
| 227 | + } |
| 228 | + |
| 229 | + /** |
| 230 | + * Draws borders via css in the question table according to |
| 231 | + * controller's category spans (metacategories) |
| 232 | + * todo: this function takes too much arguments; |
| 233 | + * figure out how to split it into smaller parts |
| 234 | + */ |
| 235 | + function renderSpan( &$row, &$name, $value, $catId, $catDesc, &$text ) { |
| 236 | + $spanState = &$this->spanState; |
| 237 | + if ( array_key_exists( 'spanId', $catDesc ) ) { |
| 238 | + $spanState->id = $catDesc[ 'spanId' ]; |
| 239 | + $name .= 's' . $spanState->id; |
| 240 | + # there can't be more category spans than the categories |
| 241 | + if ( $this->ctrl->mCategorySpans[ $spanState->id ]['count'] > count( $this->ctrl->mCategories ) ) { |
| 242 | + $text = $this->bodyErrorMessage( wfMsg( 'qp_error_too_many_spans' ), 'error' ) . $text; |
| 243 | + $this->rawClass = 'proposalerror'; |
| 244 | + } |
| 245 | + if ( $spanState->prevId != $spanState->id ) { |
| 246 | + # begin of new span |
| 247 | + $spanState->wasChecked = false; |
| 248 | + $spanState->prevId = $spanState->id; |
| 249 | + $spanState->cellsLeft = $this->ctrl->mCategorySpans[ $spanState->id ]['count']; |
| 250 | + if ( $spanState->cellsLeft < 2 ) { |
| 251 | + $text = $this->bodyErrorMessage( wfMsg( 'qp_error_too_few_spans' ), 'error' ) . $text; |
| 252 | + QP_Renderer::addClass( $row[ $catId ], 'error' ); |
| 253 | + } |
| 254 | + $spanState->isDrawing = $spanState->cellsLeft != 1 && $spanState->cellsLeft != count( $this->ctrl->mCategories ); |
| 255 | + # hightlight only spans of count != 1 and count != count(categories) |
| 256 | + if ( $spanState->isDrawing ) { |
| 257 | + $spanState->className = $this->signClass[ 'first' ]; |
| 258 | + } |
| 259 | + } else { |
| 260 | + $spanState->cellsLeft--; |
| 261 | + if ( $spanState->isDrawing ) { |
| 262 | + $spanState->className = ( $spanState->cellsLeft > 1 ) ? $this->signClass[ 'middle' ] : $this->signClass[ 'last' ]; |
| 263 | + } |
| 264 | + } |
| 265 | + } |
| 266 | + # todo: figure out how to split this part to separate function |
| 267 | + # this part is unneeded for question stats controller, |
| 268 | + # which should never have $this->ctrl->poll->mBeingCorrected === true |
| 269 | + if ( $spanState->cellsLeft <= 1 ) { |
| 270 | + # end of new span |
| 271 | + if ( $this->ctrl->poll->mBeingCorrected && |
| 272 | + !$spanState->wasChecked && |
| 273 | + $this->ctrl->mRequest->getVal( $name ) != $value ) { |
| 274 | + # the span (a part of proposal) was submitted but unanswered |
| 275 | + $text = $this->bodyErrorMessage( wfMsg( 'qp_error_unanswered_span' ), 'NA' ) . $text; |
| 276 | + # highlight current span to indicate an error |
| 277 | + for ( $i = $catId, $j = $this->ctrl->mCategorySpans[ $spanState->id ]['count']; $j > 0; $i--, $j-- ) { |
| 278 | + QP_Renderer::addClass( $row[$i], 'error' ); |
| 279 | + } |
| 280 | + $this->rawClass = 'proposalerror'; |
| 281 | + } |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + /** |
| 286 | + * Renders question table with header and proposal views |
| 287 | + */ |
| 288 | + function renderTable() { |
| 289 | + $questionTable = array(); |
| 290 | + # add header views to $questionTable |
| 291 | + foreach ( $this->hview as $header ) { |
| 292 | + $rowattrs = ''; |
| 293 | + $attribute_maps = null; |
| 294 | + if ( is_object( $header ) ) { |
| 295 | + $row = &$header->row; |
| 296 | + $rowattrs = array( 'class' => $header->className ); |
| 297 | + $attribute_maps = &$header->attribute_maps; |
| 298 | + } else { |
| 299 | + $row = &$header; |
| 300 | + } |
| 301 | + if ( $this->transposed ) { |
| 302 | + qp_Renderer::addColumn( $questionTable, $row, $rowattrs, 'th', $attribute_maps ); |
| 303 | + } else { |
| 304 | + qp_Renderer::addRow( $questionTable, $row, $rowattrs, 'th', $attribute_maps ); |
| 305 | + } |
| 306 | + } |
| 307 | + # add proposal views to $questionTable |
| 308 | + ksort( $this->pview ); |
| 309 | + foreach ( $this->pview as $propview ) { |
| 310 | + $row = &$propview->row; |
| 311 | + $rowattrs = array( 'class' => $propview->className ); |
| 312 | + $text = array( '__tag' => 'td', '__end' => "\n", 'class' => 'proposaltext', 'style' => $this->proposalTextStyle, 0 => $this->rtp( $propview->text ) ); |
| 313 | + # insert proposal text to the beginning / end according to proposalsFirst property |
| 314 | + if ( $this->proposalsFirst ) { |
| 315 | + # first element is proposaltext |
| 316 | + array_unshift( $row, $text ); |
| 317 | + } else { |
| 318 | + # last element is proposaltext |
| 319 | + $row[] = $text; |
| 320 | + } |
| 321 | + if ( $this->transposed ) { |
| 322 | + qp_Renderer::addColumn( $questionTable, $row, $rowattrs ); |
| 323 | + } else { |
| 324 | + qp_Renderer::addRow( $questionTable, $row, $rowattrs ); |
| 325 | + } |
| 326 | + } |
| 327 | + return $questionTable; |
| 328 | + } |
| 329 | + |
| 330 | + /*** cell templates ***/ |
| 331 | + |
| 332 | + function hasShowResults() { |
| 333 | + return $this->showResults['type'] != 0 && |
| 334 | + method_exists( $this, 'addShowResults' . $this->showResults['type'] ); |
| 335 | + } |
| 336 | + |
| 337 | + function addShowResults( $inp, $proposalId, $catId ) { |
| 338 | + return $this->{ 'addShowResults' . $this->showResults['type'] }( $inp, $proposalId, $catId ); |
| 339 | + } |
| 340 | + |
| 341 | + # cell templates for the selected showresults |
| 342 | + var $cellTemplate = Array(); |
| 343 | + var $cellTemplateParam = Array( 'inp' => '', 'percents' => '', 'bar1style' => '', 'bar2style' => '' ); |
| 344 | + |
| 345 | + # setup a template for showresults=1 |
| 346 | + # showresults=1 cellTemplate has only one variant |
| 347 | + function cellTemplate1() { |
| 348 | + $this->cellTemplate = |
| 349 | + array( |
| 350 | + 0 => array( '__tag' => 'div', 0 => &$this->cellTemplateParam['inp'] ), |
| 351 | + 1 => array( '__tag' => 'div', 'class' => 'stats', 0 => &$this->cellTemplateParam['percents'] ) |
| 352 | + ); |
| 353 | + if ( isset( $this->showResults['color'] ) ) { |
| 354 | + $this->cellTemplate[1]['style'] = 'color:' . $this->showResults['color'] . ';'; |
| 355 | + } |
| 356 | + if ( isset( $this->showResults['background'] ) ) { |
| 357 | + $this->cellTemplate[1]['style'] .= 'background:' . $this->showResults['background'] . ';'; |
| 358 | + } |
| 359 | + } |
| 360 | + |
| 361 | + # transform input according to showresults=1 (numerical percents) |
| 362 | + # *** warning! parameters should be passed only by value, not by reference *** |
| 363 | + function addShowResults1( $inp, $proposalId, $catId ) { |
| 364 | + $this->cellTemplateParam['inp'] = $inp; |
| 365 | + $this->cellTemplateParam['percents'] = ' '; |
| 366 | + if ( ( $percents = $this->ctrl->getPercents( $proposalId, $catId ) ) !== false ) { |
| 367 | + # there is a stat in cell |
| 368 | + $this->cellTemplateParam['percents'] = $percents . '%'; |
| 369 | + # template has to be rendered immediately, because $this->cellTemplateParam[] are used as pointers and thus, |
| 370 | + # will always be overwritten |
| 371 | + return QP_Renderer::renderTagArray( $this->cellTemplate ); |
| 372 | + } else { |
| 373 | + return $inp; |
| 374 | + } |
| 375 | + } |
| 376 | + |
| 377 | + # setup a template for showresults=2 |
| 378 | + function cellTemplate2() { |
| 379 | + # statical styles |
| 380 | + $percentstyle = ''; |
| 381 | + if ( isset( $this->showResults['textcolor'] ) ) { |
| 382 | + $percentstyle = 'color:' . $this->showResults['textcolor'] . ';'; |
| 383 | + } |
| 384 | + if ( isset( $this->showResults['textbackground'] ) ) { |
| 385 | + $percentstyle .= 'background:' . $this->showResults['textbackground'] . ';'; |
| 386 | + } |
| 387 | + # html arrays used in templates below |
| 388 | + $bar = array( '__tag' => 'div', 'class' => 'stats1', |
| 389 | + 0 => array( '__tag' => 'div', 'class' => 'bar0', 0 => &$this->cellTemplateParam['inp'] ), |
| 390 | + 1 => array( '__tag' => 'div', 'class' => 'bar1', 'style' => &$this->cellTemplateParam['bar1style'], 0 => ' ' ), |
| 391 | + 2 => array( '__tag' => 'div', 'class' => 'bar2', 'style' => &$this->cellTemplateParam['bar2style'], 0 => ' ' ), |
| 392 | + 3 => array( '__tag' => 'div', 'class' => 'bar0', 'style' => $percentstyle, 0 => &$this->cellTemplateParam['percents'] ) |
| 393 | + ); |
| 394 | + $bar2 = array( '__tag' => 'div', 'class' => 'stats1', |
| 395 | + 0 => array( '__tag' => 'div', 'class' => 'bar0', 0 => ' ' ), |
| 396 | + 1 => &$bar[1], |
| 397 | + 2 => &$bar[2], |
| 398 | + 3 => &$bar[3] |
| 399 | + ); |
| 400 | + # has two available templates ('bar','textinput') |
| 401 | + $this->cellTemplate = array( |
| 402 | + 'bar' => $bar, |
| 403 | + 'textinput' => array( '__tag' => 'table', 'class' => 'stats', |
| 404 | + 0 => array( '__tag' => 'tr', |
| 405 | + 0 => array( '__tag' => 'td', 0 => &$this->cellTemplateParam['inp'] ), |
| 406 | + ), |
| 407 | + 1 => array( '__tag' => 'tr', |
| 408 | + 0 => array( '__tag' => 'td', |
| 409 | + 0 => $bar2 |
| 410 | + ) |
| 411 | + ) |
| 412 | + ), |
| 413 | + # the following entries are not real templates, but pre-calculated values of css attributes taken from showresults parameter |
| 414 | + 'bar1showres' => '', |
| 415 | + 'bar2showres' => '' |
| 416 | + ); |
| 417 | + # dynamical styles, width: in percents will be added during rendering in addShowResults |
| 418 | + if ( isset( $this->showResults['color'] ) ) { |
| 419 | + $this->cellTemplate['bar1showres'] .= 'background:' . $this->showResults['color'] . ';'; |
| 420 | + } |
| 421 | + if ( isset( $this->showResults['background'] ) ) { |
| 422 | + $this->cellTemplate['bar2showres'] .= 'background:' . $this->showResults['background'] . ';'; |
| 423 | + } |
| 424 | + } |
| 425 | + |
| 426 | + # transform input according to showresults=2 (bars) |
| 427 | + # *** warning! parameters should be passed only by value, not by reference *** |
| 428 | + function addShowResults2( $inp, $proposalId, $catId ) { |
| 429 | + $this->cellTemplateParam['inp'] = $inp; |
| 430 | + $this->cellTemplateParam['percents'] = ' '; |
| 431 | + if ( ( $percents = $this->ctrl->getPercents( $proposalId, $catId ) ) !== false ) { |
| 432 | + # there is a stat in cell |
| 433 | + $this->cellTemplateParam['percents'] = $percents . '%'; |
| 434 | + $this->cellTemplateParam['bar1style'] = 'width:' . $percents . 'px;' . $this->cellTemplate[ 'bar1showres' ]; |
| 435 | + $this->cellTemplateParam['bar2style'] = 'width:' . ( 100 - $percents ) . 'px;' . $this->cellTemplate[ 'bar2showres' ]; |
| 436 | + if ( $inp['type'] == 'text' ) { |
| 437 | + return qp_Renderer::renderTagArray( $this->cellTemplate['textinput'] ); |
| 438 | + } else { |
| 439 | + return qp_Renderer::renderTagArray( $this->cellTemplate['bar'] ); |
| 440 | + } |
| 441 | + } else { |
| 442 | + return $inp; |
| 443 | + } |
| 444 | + } |
| 445 | + /*** end of cell templates ***/ |
| 446 | + |
| 447 | +} /* end of qp_TabularQuestionView class */ |
Property changes on: trunk/extensions/QPoll/view/qp_tabularquestionview.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 448 | + native |
Index: trunk/extensions/QPoll/view/qp_pollstatsview.php |
— | — | @@ -90,9 +90,9 @@ |
91 | 91 | } |
92 | 92 | if ( $this->perRow > 1 ) { |
93 | 93 | $question_table = array( '__tag' => 'table', 0 => array( '__tag' => 'tbody', 0 => &$write_row, '__end' => "\n" ), '__end' => "\n" ); |
94 | | - return qp_Renderer::renderHTMLobject( $question_table ); |
| 94 | + return qp_Renderer::renderTagArray( $question_table ); |
95 | 95 | } else { |
96 | | - return qp_Renderer::renderHTMLobject( $write_row ); |
| 96 | + return qp_Renderer::renderTagArray( $write_row ); |
97 | 97 | } |
98 | 98 | } |
99 | 99 | |
— | — | @@ -103,7 +103,7 @@ |
104 | 104 | function renderPoll() { |
105 | 105 | # Generates the output. |
106 | 106 | $qpoll_div = array( '__tag' => 'div', 'class' => 'qpoll', 0 => $this->renderQuestionViews() ); |
107 | | - return qp_Renderer::renderHTMLobject( $qpoll_div ); |
| 107 | + return qp_Renderer::renderTagArray( $qpoll_div ); |
108 | 108 | } |
109 | 109 | |
110 | 110 | } /* end of qp_PollStatsView class */ |
Index: trunk/extensions/QPoll/view/qp_stubquestionview.php |
— | — | @@ -0,0 +1,218 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * ***** BEGIN LICENSE BLOCK ***** |
| 5 | + * This file is part of QPoll. |
| 6 | + * Uses parts of code from Quiz extension (c) 2007 Louis-Rémi BABE. All rights reserved. |
| 7 | + * |
| 8 | + * QPoll is free software; you can redistribute it and/or modify |
| 9 | + * it under the terms of the GNU General Public License as published by |
| 10 | + * the Free Software Foundation; either version 2 of the License, or |
| 11 | + * (at your option) any later version. |
| 12 | + * |
| 13 | + * QPoll is distributed in the hope that it will be useful, |
| 14 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | + * GNU General Public License for more details. |
| 17 | + * |
| 18 | + * You should have received a copy of the GNU General Public License |
| 19 | + * along with QPoll; if not, write to the Free Software |
| 20 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 21 | + * |
| 22 | + * ***** END LICENSE BLOCK ***** |
| 23 | + * |
| 24 | + * QPoll is a poll tool for MediaWiki. |
| 25 | + * |
| 26 | + * To activate this extension : |
| 27 | + * * Create a new directory named QPoll into the directory "extensions" of MediaWiki. |
| 28 | + * * Place the files from the extension archive there. |
| 29 | + * * Add this line at the end of your LocalSettings.php file : |
| 30 | + * require_once "$IP/extensions/QPoll/qp_user.php"; |
| 31 | + * |
| 32 | + * @version 0.8.0a |
| 33 | + * @link http://www.mediawiki.org/wiki/Extension:QPoll |
| 34 | + * @author QuestPC <questpc@rambler.ru> |
| 35 | + */ |
| 36 | + |
| 37 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 38 | + die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * Stub view is used only to display questions which has errors; |
| 43 | + * However, this view also shows the _miminal_ set of methods that |
| 44 | + * real question view has to be implemented to work with pair controller |
| 45 | + * and base controller. |
| 46 | + */ |
| 47 | +class qp_StubQuestionView extends qp_AbstractView { |
| 48 | + |
| 49 | + # error message which occured during the question header parsing that will be output later at rendering stage |
| 50 | + var $headerErrorMessage = 'Unknown error'; |
| 51 | + |
| 52 | + # begin of proposalView |
| 53 | + # these vars (and perhaps some more) should be part of separate proposalView, |
| 54 | + # todo: in the future create separate proposal view with $row and $text ? |
| 55 | + var $rawClass; |
| 56 | + # end of proposalView |
| 57 | + |
| 58 | + var $hview = array(); |
| 59 | + # proposal views (indexed, sortable rows) |
| 60 | + var $pview = array(); |
| 61 | + |
| 62 | + /** |
| 63 | + * @param $parser |
| 64 | + * @param $frame |
| 65 | + */ |
| 66 | + function __construct( &$parser, &$frame ) { |
| 67 | + parent::__construct( $parser, $frame ); |
| 68 | + } |
| 69 | + |
| 70 | + /** |
| 71 | + * Called for every proposal of the question |
| 72 | + * todo: actually should be a member of separate small proposal view class |
| 73 | + */ |
| 74 | + function initProposalView() { |
| 75 | + $this->rawClass = 'proposal'; |
| 76 | + } |
| 77 | + |
| 78 | + static function newFromBaseView( $baseView ) { |
| 79 | + return new self( $baseView->parser, $baseView->ppframe ); |
| 80 | + } |
| 81 | + |
| 82 | + function isCompatibleController( $ctrl ) { |
| 83 | + return method_exists( $ctrl, 'parseBody' ); |
| 84 | + } |
| 85 | + |
| 86 | + function setLayout( $layout, $textwidth ) { |
| 87 | + /* does nothing */ |
| 88 | + } |
| 89 | + |
| 90 | + function setShowResults( $showresults ) { |
| 91 | + /* does nothing */ |
| 92 | + } |
| 93 | + |
| 94 | + /** |
| 95 | + * @param $tagarray array / string row to add to the question's header |
| 96 | + */ |
| 97 | + function addHeader( $tagarray ) { |
| 98 | + $this->hview[] = $tagarray; |
| 99 | + } |
| 100 | + |
| 101 | + function addHeaderError() { |
| 102 | + $this->hview[] = array( |
| 103 | + array( '__tag' => 'td', 'class' => 'proposalerror', $this->headerErrorMessage ) |
| 104 | + ); |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * Adds table header row to question's view |
| 109 | + * @param $row tagarray representation of row |
| 110 | + * @param $className CSS class name of row |
| 111 | + * @param $attribute_maps translation of source attributes into html attributes (see qp_Renderer class) |
| 112 | + */ |
| 113 | + function addHeaderRow( $row, $className, $attribute_maps = null ) { |
| 114 | + $this->hview[] = (object) array( |
| 115 | + 'row' => $row, |
| 116 | + 'className' => $className, |
| 117 | + 'attribute_maps' => $attribute_maps |
| 118 | + ); |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Outputs question body parser error/warning message; also set new controller state |
| 123 | + * @param $msg - text of message |
| 124 | + * @param $state - set new question controller state |
| 125 | + * note that the 'error' state cannot be changed and '' state cannot be set |
| 126 | + * @param $rawClass - string set rawClass value or false (do not set) |
| 127 | + */ |
| 128 | + function bodyErrorMessage( $msg, $state, $rawClass = 'proposalerror' ) { |
| 129 | + $prev_state = $this->ctrl->getState(); |
| 130 | + # do not clear previous errors (do not call setState() when $state == '') |
| 131 | + if ( $state != '' ) { |
| 132 | + $this->ctrl->setState( $state, $msg ); |
| 133 | + } |
| 134 | + # return the message only for the first error occured |
| 135 | + # (this one has to be short, because title attribute is being used) |
| 136 | + if ( is_string( $rawClass ) ) { |
| 137 | + $this->rawClass = $rawClass; |
| 138 | + } |
| 139 | + # show only the first error, when the state is not clean (not '') |
| 140 | + return ( $prev_state == '' ) ? '<span class="proposalerror" title="' . qp_Setup::specialchars( $msg ) . '">???</span> ' : ''; |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * Render script-generated proposal errors, when available (quiz mode) |
| 145 | + * Note: not being called in stats mode |
| 146 | + * todo: implement separate category error hints in addition to the |
| 147 | + * whole proposal row error hints (especially useful for text questions) |
| 148 | + */ |
| 149 | + function renderInterpErrors() { |
| 150 | + if ( ( $propErrors = $this->ctrl->getProposalsErrors() ) === false ) { |
| 151 | + return; |
| 152 | + } |
| 153 | + foreach ( $this->pview as $prop_id => &$propview ) { |
| 154 | + if ( isset( $propErrors[$prop_id] ) ) { |
| 155 | + $msg = is_string( $propErrors[$prop_id] ) ? $propErrors[$prop_id] : wfMsg( 'qp_interpetation_wrong_answer' ); |
| 156 | + $propview->text = $this->bodyErrorMessage( $msg, '', false ) . $propview->text; |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Renders question table with header and proposal views |
| 163 | + */ |
| 164 | + function renderTable() { |
| 165 | + $questionTable = array(); |
| 166 | + # add header views to $questionTable |
| 167 | + foreach ( $this->hview as $header ) { |
| 168 | + $rowattrs = ''; |
| 169 | + $attribute_maps = null; |
| 170 | + if ( is_object( $header ) ) { |
| 171 | + $row = &$header->row; |
| 172 | + $rowattrs = array( 'class' => $header->className ); |
| 173 | + $attribute_maps = &$header->attribute_maps; |
| 174 | + } else { |
| 175 | + $row = &$header; |
| 176 | + } |
| 177 | + qp_Renderer::addRow( $questionTable, $row, $rowattrs, 'th', $attribute_maps ); |
| 178 | + } |
| 179 | + return $questionTable; |
| 180 | + } |
| 181 | + |
| 182 | + /** |
| 183 | + * todo: unfortunately, rendering of the question also conditionally modifies |
| 184 | + * state of poll controller |
| 185 | + * @modifies parent controller |
| 186 | + * @return string html representation of the question |
| 187 | + */ |
| 188 | + function renderQuestion() { |
| 189 | + $output_table = array( '__tag' => 'table', '__end' => "\n", 'class' => 'object' ); |
| 190 | + # Determine the side border color the question. |
| 191 | + if ( $this->ctrl->getState() != '' ) { |
| 192 | + if ( isset( $output_table['class'] ) ) { |
| 193 | + $output_table['class'] .= ' error_mark'; |
| 194 | + } else { |
| 195 | + $output_table['class'] = 'error_mark'; |
| 196 | + } |
| 197 | + # set poll controller state according to question controller state |
| 198 | + $this->ctrl->applyStateToParent(); |
| 199 | + } |
| 200 | + $output_table[] = array( '__tag' => 'tbody', '__end' => "\n", 0 => $this->renderTable() ); |
| 201 | + $tags = array( '__tag' => 'div', '__end' => "\n", 'class' => 'question', |
| 202 | + 0 => array( '__tag' => 'div', '__end' => "\n", 'class' => 'header', |
| 203 | + 0 => array( '__tag' => 'span', 'class' => 'questionId', 0 => $this->ctrl->usedId ) |
| 204 | + ), |
| 205 | + 1 => array( '__tag' => 'div', 0 => $this->rtp( $this->ctrl->mCommonQuestion ) ) |
| 206 | + ); |
| 207 | + $tags[] = &$output_table; |
| 208 | + return qp_Renderer::renderTagArray( $tags ); |
| 209 | + } |
| 210 | + |
| 211 | + /*** cell templates ***/ |
| 212 | + |
| 213 | + function hasShowResults() { |
| 214 | + return false; |
| 215 | + } |
| 216 | + |
| 217 | + /*** end of cell templates ***/ |
| 218 | + |
| 219 | +} /* end of qp_StubQuestionView class */ |
Property changes on: trunk/extensions/QPoll/view/qp_stubquestionview.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 220 | + native |
Index: trunk/extensions/QPoll/view/qp_pollview.php |
— | — | @@ -88,9 +88,9 @@ |
89 | 89 | } |
90 | 90 | if ( $this->perRow > 1 ) { |
91 | 91 | $question_table = array( '__tag' => 'table', 0 => array( '__tag' => 'tbody', 0 => &$write_row, '__end' => "\n" ), '__end' => "\n" ); |
92 | | - return qp_Renderer::renderHTMLobject( $question_table ); |
| 92 | + return qp_Renderer::renderTagArray( $question_table ); |
93 | 93 | } else { |
94 | | - return qp_Renderer::renderHTMLobject( $write_row ); |
| 94 | + return qp_Renderer::renderTagArray( $write_row ); |
95 | 95 | } |
96 | 96 | } |
97 | 97 | |
— | — | @@ -112,7 +112,7 @@ |
113 | 113 | $qpoll_div[] = array( '__tag' => 'div', 'class' => 'interp_answer', qp_Setup::specialchars( $longAnswer ) ); |
114 | 114 | } |
115 | 115 | # create voting form and fill it with messages and inputs |
116 | | - $qpoll_form = array( '__tag' => 'form', 'method' => 'post', 'action' => $this->ctrl->getPollTitleFragment(), '__end' => "\n" ); |
| 116 | + $qpoll_form = array( '__tag' => 'form', 'method' => 'post', 'action' => $this->ctrl->getPollTitleFragment(), 'autocomplete' => 'off', '__end' => "\n" ); |
117 | 117 | $qpoll_div[] = &$qpoll_form; |
118 | 118 | # Determine the content of the settings table. |
119 | 119 | $settings = Array(); |
— | — | @@ -163,7 +163,7 @@ |
164 | 164 | } |
165 | 165 | |
166 | 166 | $qpoll_form[] = &$p; |
167 | | - return qp_Renderer::renderHTMLobject( $qpoll_div ); |
| 167 | + return qp_Renderer::renderTagArray( $qpoll_div ); |
168 | 168 | } |
169 | 169 | |
170 | 170 | } /* end of qp_PollView class */ |
Index: trunk/extensions/QPoll/view/qp_questionstatsview.php |
— | — | @@ -42,10 +42,10 @@ |
43 | 43 | * allows to modify these for quizes results at the later stage (see qp_poll.php) |
44 | 44 | * todo: transfer view logic completely from controllers |
45 | 45 | */ |
46 | | -class qp_QuestionStatsView extends qp_QuestionView { |
| 46 | +class qp_QuestionStatsView extends qp_TabularQuestionView { |
47 | 47 | |
48 | 48 | static function newFromBaseView( $view ) { |
49 | | - return new qp_QuestionStatsView( $view->parser, $view->ppframe, $view->showResults ); |
| 49 | + return new self( $view->parser, $view->ppframe, $view->showResults ); |
50 | 50 | } |
51 | 51 | |
52 | 52 | function isCompatibleController( $ctrl ) { |
— | — | @@ -96,7 +96,7 @@ |
97 | 97 | $this->cellTemplateParam['percents'] = $percents . '%'; |
98 | 98 | # template has to be rendered immediately, because $this->cellTemplateParam[] are used as pointers and thus, |
99 | 99 | # will always be overwritten |
100 | | - return QP_Renderer::renderHTMLobject( $this->cellTemplate ); |
| 100 | + return QP_Renderer::renderTagArray( $this->cellTemplate ); |
101 | 101 | } else { |
102 | 102 | return ''; |
103 | 103 | } |
— | — | @@ -141,7 +141,7 @@ |
142 | 142 | $this->cellTemplateParam['percents'] = $percents . '%'; |
143 | 143 | $this->cellTemplateParam['bar1style'] = 'width:' . $percents . 'px;' . $this->cellTemplate[ 'bar1showres' ]; |
144 | 144 | $this->cellTemplateParam['bar2style'] = 'width:' . ( 100 - $percents ) . 'px;' . $this->cellTemplate[ 'bar2showres' ]; |
145 | | - return qp_Renderer::renderHTMLobject( $this->cellTemplate['bar'] ); |
| 145 | + return qp_Renderer::renderTagArray( $this->cellTemplate['bar'] ); |
146 | 146 | } else { |
147 | 147 | return ''; |
148 | 148 | } |
Index: trunk/extensions/QPoll/view/qp_textquestionview.php |
— | — | @@ -0,0 +1,273 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * ***** BEGIN LICENSE BLOCK ***** |
| 5 | + * This file is part of QPoll. |
| 6 | + * Uses parts of code from Quiz extension (c) 2007 Louis-Rémi BABE. All rights reserved. |
| 7 | + * |
| 8 | + * QPoll is free software; you can redistribute it and/or modify |
| 9 | + * it under the terms of the GNU General Public License as published by |
| 10 | + * the Free Software Foundation; either version 2 of the License, or |
| 11 | + * (at your option) any later version. |
| 12 | + * |
| 13 | + * QPoll is distributed in the hope that it will be useful, |
| 14 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | + * GNU General Public License for more details. |
| 17 | + * |
| 18 | + * You should have received a copy of the GNU General Public License |
| 19 | + * along with QPoll; if not, write to the Free Software |
| 20 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 21 | + * |
| 22 | + * ***** END LICENSE BLOCK ***** |
| 23 | + * |
| 24 | + * QPoll is a poll tool for MediaWiki. |
| 25 | + * |
| 26 | + * To activate this extension : |
| 27 | + * * Create a new directory named QPoll into the directory "extensions" of MediaWiki. |
| 28 | + * * Place the files from the extension archive there. |
| 29 | + * * Add this line at the end of your LocalSettings.php file : |
| 30 | + * require_once "$IP/extensions/QPoll/qp_user.php"; |
| 31 | + * |
| 32 | + * @version 0.8.0a |
| 33 | + * @link http://www.mediawiki.org/wiki/Extension:QPoll |
| 34 | + * @author QuestPC <questpc@rambler.ru> |
| 35 | + */ |
| 36 | + |
| 37 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 38 | + die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * Manipulates the list of text question view tokens |
| 43 | + * for one row (combined proposal/categories definition) |
| 44 | + */ |
| 45 | +class qp_TextQuestionViewTokens { |
| 46 | + |
| 47 | + # an instance of current question's view |
| 48 | + var $view; |
| 49 | + |
| 50 | + # $tokenslist will contain parsed tokens for question view; |
| 51 | + # $tokenslist elements of string type contain proposal parts; |
| 52 | + # $tokenslist elements of stdClass : |
| 53 | + # property 'options' indicates current category options list |
| 54 | + # property 'error' indicates error message |
| 55 | + var $tokenslist = array(); |
| 56 | + |
| 57 | + function __construct( qp_TextQuestionView $view ) { |
| 58 | + $this->view = $view; |
| 59 | + } |
| 60 | + |
| 61 | + function reset() { |
| 62 | + $this->tokenslist = array(); |
| 63 | + } |
| 64 | + |
| 65 | + function addProposalPart( $prop ) { |
| 66 | + $this->tokenslist[] = $prop; |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * Creates viewtokens entry with current category definition |
| 71 | + * @param $opt qp_TextQuestionOptions |
| 72 | + * should contain "closed" category definition with prepared |
| 73 | + * category input options |
| 74 | + * @param $name string name of input/select element (used in the view) |
| 75 | + * @param $text_answer string user's POSTed category answer |
| 76 | + * (empty string '' means no answer) |
| 77 | + * @return stdClass object with viewtokens entry |
| 78 | + */ |
| 79 | + function addCatDef( qp_TextQuestionOptions $opt, $name, $text_answer, $unanswered ) { |
| 80 | + # $catdef instanceof stdClass properties: |
| 81 | + # property 'options' stores an array of user options |
| 82 | + # Multiple options will be selected from the list |
| 83 | + # Single option will be displayed as text input |
| 84 | + # property 'name' contains name of input element |
| 85 | + # property 'value' contains value previousely chosen |
| 86 | + # by user (if any) |
| 87 | + # property 'textwidth' may optionally override default |
| 88 | + # text input width |
| 89 | + # property 'unanswered' boolean |
| 90 | + # true - the question was POSTed but category is unanswered |
| 91 | + # false - the question was not POSTed or category is answered |
| 92 | + $catdef = (object) array( |
| 93 | + 'options' => $opt->input_options, |
| 94 | + 'name' => $name, |
| 95 | + 'value' => $text_answer, |
| 96 | + 'unanswered' => $unanswered |
| 97 | + ); |
| 98 | + if ( !is_null( $opt->textwidth ) ) { |
| 99 | + $catdef->textwidth = $opt->textwidth; |
| 100 | + } |
| 101 | + $this->tokenslist[] = $catdef; |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Adds new non-empty error message to the list of parsed tokens (viewtokens) |
| 106 | + * @param $errmsg string error message |
| 107 | + */ |
| 108 | + function prependErrorToken( $errmsg, $state ) { |
| 109 | + # note: error message can be added in the middle of the list, |
| 110 | + # for any category, if desired |
| 111 | + # todo: separate category interpretation errors |
| 112 | + if ( ( $html_msg = $this->view->bodyErrorMessage( $errmsg, $state ) ) !== '' ) { |
| 113 | + # usually only the first error message is returned |
| 114 | + array_unshift( $this->tokenslist, (object) array( 'error'=> $html_msg ) ); |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | +} /* end of qp_TextQuestionViewTokens */ |
| 119 | + |
| 120 | +/** |
| 121 | + * Stores question proposals views (see qp_textqestion.php) and |
| 122 | + * allows to modify these for results of quizes at the later stage (see qp_poll.php) |
| 123 | + * An attempt to make somewhat cleaner question view |
| 124 | + * todo: further refactoring of views for different question types |
| 125 | + */ |
| 126 | +class qp_TextQuestionView extends qp_StubQuestionView { |
| 127 | + |
| 128 | + var $textInputStyle = ''; |
| 129 | + |
| 130 | + /** |
| 131 | + * @param $parser |
| 132 | + * @param $frame |
| 133 | + * @param $showResults poll's showResults (may be overriden in the question) |
| 134 | + */ |
| 135 | + function __construct( &$parser, &$frame, $showResults ) { |
| 136 | + parent::__construct( $parser, $frame ); |
| 137 | + /* todo: implement showResults */ |
| 138 | + } |
| 139 | + |
| 140 | + static function newFromBaseView( $baseView ) { |
| 141 | + return new self( $baseView->parser, $baseView->ppframe, $baseView->showResults ); |
| 142 | + } |
| 143 | + |
| 144 | + function setLayout( $layout, $textwidth ) { |
| 145 | + /* todo: implement vertical layout */ |
| 146 | + if ( count( $textwidth ) > 0 ) { |
| 147 | + $textwidth = intval( $textwidth[1] ); |
| 148 | + if ( $textwidth > 0 ) { |
| 149 | + $this->textInputStyle = 'width:' . $textwidth . 'em;'; |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Add the list of parsed viewtokens matching current proposal / categories row |
| 156 | + */ |
| 157 | + function addProposal( $proposalId, $viewtokens ) { |
| 158 | + $this->pview[$proposalId] = array( |
| 159 | + 'tokens' => $viewtokens, |
| 160 | + 'className' => $this->rawClass |
| 161 | + ); |
| 162 | + } |
| 163 | + |
| 164 | + /** |
| 165 | + * |
| 166 | + */ |
| 167 | + function renderParsedProposal( &$viewtokens ) { |
| 168 | + $row = array(); |
| 169 | + foreach ( $viewtokens as $elem ) { |
| 170 | + if ( is_object( $elem ) ) { |
| 171 | + if ( isset( $elem->options ) ) { |
| 172 | + $className = $elem->unanswered ? 'cat_noanswer' : 'cat_part'; |
| 173 | + # create view for the input options part |
| 174 | + if ( count( $elem->options ) === 1 ) { |
| 175 | + # one option produces html text input |
| 176 | + $value = $elem->value; |
| 177 | + # check, whether the definition of category has "pre-filled" value |
| 178 | + # single, non-unanswered, non-empty option is a pre-filled value |
| 179 | + if ( !$elem->unanswered && $elem->value === '' && $elem->options[0] !== '' ) { |
| 180 | + # input text pre-fill |
| 181 | + $value = $elem->options[0]; |
| 182 | + $className = 'cat_prefilled'; |
| 183 | + } |
| 184 | + $input = array( |
| 185 | + '__tag' => 'input', |
| 186 | + 'class' => $className, |
| 187 | + 'type' => 'text', |
| 188 | + 'name' => $elem->name, |
| 189 | + 'value' => qp_Setup::specialchars( $value ) |
| 190 | + ); |
| 191 | + if ( $this->textInputStyle != '' ) { |
| 192 | + # apply poll's textwidth attribute |
| 193 | + $input['style'] = $this->textInputStyle; |
| 194 | + } |
| 195 | + if ( isset( $elem->textwidth ) ) { |
| 196 | + # apply current category textwidth "option" |
| 197 | + $input['style'] = 'width:' . intval( $elem->textwidth ) . 'em;'; |
| 198 | + } |
| 199 | + $row[] = $input; |
| 200 | + continue; |
| 201 | + } |
| 202 | + # multiple options produce html select / options |
| 203 | + if ( $elem->options[0] !== '' ) { |
| 204 | + # default element in select/option set always must be empty option |
| 205 | + array_unshift( $elem->options, '' ); |
| 206 | + } |
| 207 | + $html_options = array(); |
| 208 | + foreach ( $elem->options as $option ) { |
| 209 | + $html_option = array( |
| 210 | + '__tag' => 'option', |
| 211 | + 'value' => qp_Setup::entities( $option ), |
| 212 | + qp_Setup::specialchars( $option ) |
| 213 | + ); |
| 214 | + if ( $option === $elem->value ) { |
| 215 | + $html_option['selected'] = 'selected'; |
| 216 | + } |
| 217 | + $html_options[] = $html_option; |
| 218 | + } |
| 219 | + $row[] = array( |
| 220 | + '__tag' => 'select', |
| 221 | + 'class' => $className, |
| 222 | + 'name' => $elem->name, |
| 223 | + $html_options |
| 224 | + ); |
| 225 | + } elseif ( isset( $elem->error ) ) { |
| 226 | + # create view for proposal/category error message |
| 227 | + $row[] = array( |
| 228 | + '__tag' => 'span', |
| 229 | + 'class' => 'proposalerror', |
| 230 | + $elem->error |
| 231 | + ); |
| 232 | + } else { |
| 233 | + throw new MWException( 'Invalid view token encountered in ' . __METHOD__ ); |
| 234 | + } |
| 235 | + } else { |
| 236 | + # create view for the proposal part |
| 237 | + $row[] = array( |
| 238 | + '__tag' => 'span', |
| 239 | + 'class' => 'prop_part', |
| 240 | + $this->rtp( $elem ) |
| 241 | + ); |
| 242 | + } |
| 243 | + } |
| 244 | + # todo: add class for errors |
| 245 | + return array( $row ); |
| 246 | + } |
| 247 | + |
| 248 | + /** |
| 249 | + * Renders question table with header and proposal views |
| 250 | + */ |
| 251 | + function renderTable() { |
| 252 | + $questionTable = array(); |
| 253 | + # add header views to $questionTable |
| 254 | + foreach ( $this->hview as $header ) { |
| 255 | + $rowattrs = ''; |
| 256 | + $attribute_maps = null; |
| 257 | + if ( is_object( $header ) ) { |
| 258 | + $row = &$header->row; |
| 259 | + $rowattrs = array( 'class' => $header->className ); |
| 260 | + $attribute_maps = &$header->attribute_maps; |
| 261 | + } else { |
| 262 | + $row = &$header; |
| 263 | + } |
| 264 | + qp_Renderer::addRow( $questionTable, $row, $rowattrs, 'th', $attribute_maps ); |
| 265 | + } |
| 266 | + foreach ( $this->pview as &$viewtokens ) { |
| 267 | + $prop = $this->renderParsedProposal( $viewtokens['tokens'] ); |
| 268 | + $rowattrs = array( 'class' => $viewtokens['className'] ); |
| 269 | + qp_Renderer::addRow( $questionTable, $prop, $rowattrs ); |
| 270 | + } |
| 271 | + return $questionTable; |
| 272 | + } |
| 273 | + |
| 274 | +} /* end of qp_TextQuestionView class */ |
Property changes on: trunk/extensions/QPoll/view/qp_textquestionview.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 275 | + native |