r97037 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r97036‎ | r97037 | r97038 >
Date:06:54, 14 September 2011
Author:questpc
Status:deferred
Tags:
Comment:
Questions of text type are functional with the exception of their interpretation. Their interpretation logic is not implemented yet.
Modified paths:
  • /trunk/extensions/QPoll/clientside/qp_results.css (modified) (history)
  • /trunk/extensions/QPoll/clientside/qp_user.css (modified) (history)
  • /trunk/extensions/QPoll/clientside/qp_user.js (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_abstractquestion.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_mixedquestion.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_poll.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_questionstats.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_stubquestion.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_tabularquestion.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/qp_textquestion.php (added) (history)
  • /trunk/extensions/QPoll/i18n/qp.i18n.php (modified) (history)
  • /trunk/extensions/QPoll/qp_pollstore.php (modified) (history)
  • /trunk/extensions/QPoll/qp_questiondata.php (added) (history)
  • /trunk/extensions/QPoll/qp_results.php (modified) (history)
  • /trunk/extensions/QPoll/qp_user.php (modified) (history)
  • /trunk/extensions/QPoll/view/qp_pollstatsview.php (modified) (history)
  • /trunk/extensions/QPoll/view/qp_pollview.php (modified) (history)
  • /trunk/extensions/QPoll/view/qp_questionstatsview.php (modified) (history)
  • /trunk/extensions/QPoll/view/qp_questionview.php (deleted) (history)
  • /trunk/extensions/QPoll/view/qp_stubquestionview.php (added) (history)
  • /trunk/extensions/QPoll/view/qp_tabularquestionview.php (added) (history)
  • /trunk/extensions/QPoll/view/qp_textquestionview.php (added) (history)

Diff [purge]

Index: trunk/extensions/QPoll/i18n/qp.i18n.php
@@ -116,6 +116,7 @@
117117 'qp_error_non_unique_choice' => 'This question requires unique proposal answer.',
118118 'qp_error_category_name_empty' => 'Category name is empty.',
119119 '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',
120121 'qp_error_too_few_categories' => 'At least two categories must be defined.',
121122 'qp_error_too_few_spans' => 'Every category group must contain at least two subcategories.',
122123 'qp_error_no_answer' => 'Unanswered proposal.',
@@ -2615,6 +2616,7 @@
26162617 'qp_error_non_unique_choice' => 'Данный вопрос требует чтобы выбранный вариант ответа не использовался ранее',
26172618 'qp_error_category_name_empty' => 'Отсутствует название варианта ответа',
26182619 'qp_error_proposal_text_empty' => 'Отсутствует текст строки вопроса',
 2620+ 'qp_error_too_long_proposal_text' => 'Строка вопроса слишком длинна для сохранения в базе данных',
26192621 'qp_error_too_few_categories' => 'Каждый вопрос должен иметь по крайней мере два варианта ответа',
26202622 'qp_error_too_few_spans' => 'Каждая подкатегория вопроса требует по меньшей мере два варианта ответа',
26212623 'qp_error_no_answer' => 'Нет ответа на вопрос',
Index: trunk/extensions/QPoll/clientside/qp_user.css
@@ -9,7 +9,8 @@
1010 .qpoll table.object .spans { color:gray; }
1111 .qpoll table.object .categories { color:#444455; }
1212 .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; }
1415 .qpoll .settings td { padding: 0.1em 0.4em 0.1em 0.4em }
1516 .qpoll table.settings { background-color:transparent; }
1617 /* Part for the basic types's inputs. */
@@ -34,9 +35,10 @@
3536 .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 */
3637 .qpoll .interp_error { border: 2px solid gray; padding: 0.5em; color: white; background-color: red; }
3738 .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; }
3842
39 -.qpoll .cell_errror { background-color: #D700D7; }
40 -
4143 /* script view */
4244 .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; }
4345 .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 @@
77 .qpoll table.pollresults td.stats {background-color: Azure;}
88 .qpoll table.pollresults td.spaneven {background-color: Aquamarine;}
99 .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; }
1012 .qpoll .interp_answer { border: 1px solid gray; padding: 0; margin: 0; color: black; background-color: lightgray; font-weight:bold; line-height:2em; }
1113 .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 @@
3535
3636 (function() {
3737 function uniqueRadioButtonColumn() {
38 -// uq1c2p2
 38+ // example of input id: 'uq1c2p2'
3939 var propId, inp;
4040 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+ // ...
4647 var p = id.indexOf( 'p' ) + 1;
4748 var currPropId = parseInt( id.substring( p ) );
4849 var basepart = id.substring( 0, p );
@@ -55,7 +56,7 @@
5657 }
5758
5859 function clickMixedRow() {
59 -// mx1p2c2
 60+ // example of input id: 'mx1p2c2'
6061 var catId, inp;
6162 var id = new String( this.getAttribute('id') );
6263 var c = id.indexOf( 'c' ) + 1;
@@ -79,33 +80,54 @@
8081 }
8182 }
8283 }
 84+
 85+ function clickPrefilledText() {
 86+ if ( this.className === 'cat_prefilled' ) {
 87+ this.select();
 88+ this.className = 'cat_part';
 89+ }
 90+ }
 91+
8392 /**
8493 * Prepare the Poll for "javascriptable" browsers
8594 */
8695 function preparePoll() {
87 - var bodyContentDiv = document.getElementById('bodyContent').getElementsByTagName('div');
 96+ var bodyContentDiv = document.getElementById( 'bodyContent' ).getElementsByTagName( 'div' );
8897 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 ) {
93102 if ( input[j].id ) {
 103+ // only unique or mixed questions currently have id
94104 inputFuncId = new String( input[j].id ).slice( 0, 2 );
95105 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+ }
101111 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 );
105115 break;
106116 }
107117 } 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+ }
110132 }
111133 }
112134 }
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( " ", "&#8194;", 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
1275 + native
Index: trunk/extensions/QPoll/qp_results.php
@@ -360,58 +360,13 @@
361361 foreach ( $pollStore->Questions as &$qdata ) {
362362 if ( $pollStore->isUsedQuestion( $qdata->question_id ) ) {
363363 $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();
365366 }
366367 }
367368 return $output;
368369 }
369370
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 -
416371 private function showVotes( $pid ) {
417372 $output = "";
418373 if ( $pid !== null ) {
@@ -426,9 +381,8 @@
427382 $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n";
428383 $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'stats_xls', 'id' => $pid ) ) . "<br />\n";
429384 $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 );
433387 }
434388 }
435389 }
@@ -649,52 +603,6 @@
650604 }
651605 }
652606
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( " ", "&#8194;", 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 -
699607 static function getUsersLink() {
700608 return "<div>" . self::$UsersLink . "</div>\n";
701609 }
@@ -1023,7 +931,7 @@
1024932 $head[] = PollResults::getUsersLink();
1025933 $head[] = array( '__tag' => 'div', 'class' => 'head', 0 => $spec );
1026934 $head[] = ' (' . $goto_link . ')';
1027 - $link = qp_Renderer::renderHTMLobject( $head );
 935+ $link = qp_Renderer::renderTagArray( $head );
1028936 }
1029937 return $link;
1030938 }
@@ -1139,7 +1047,7 @@
11401048 qp_Setup::entities( $proptext ),
11411049 qp_Setup::entities( $cat_name ) ) . '<br />';
11421050 $head[] = array( '__tag' => 'div', 'class' => 'head', 'style' => 'padding-left:2em;', 0 => $qpa );
1143 - $link = qp_Renderer::renderHTMLobject( $head );
 1051+ $link = qp_Renderer::renderTagArray( $head );
11441052 }
11451053 }
11461054 }
Index: trunk/extensions/QPoll/ctrl/qp_abstractquestion.php
@@ -41,15 +41,13 @@
4242 * @public
4343 * @param $poll an instance of question's parent controller
4444 * @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
4646 */
4747 function __construct( qp_AbstractPoll $poll, qp_AbstractView $view, $questionId ) {
4848 global $wgRequest;
4949 $this->mRequest = &$wgRequest;
5050 # the question collection is not sparce by default
5151 $this->mQuestionId = $this->usedId = $questionId;
52 - $this->mProposalPattern = '`^[^\|\!].*`u';
53 - $this->mCategoryPattern = '`^\|(\n|[^\|].*\n)`u';
5452 $view->setController( $this );
5553 $this->view = $view;
5654 $this->poll = $poll;
@@ -67,7 +65,7 @@
6866 $this->mState = $pState;
6967 }
7068 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
7270 $this->view->headerErrorMessage = $error_message;
7371 }
7472 }
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
1270 + native
Index: trunk/extensions/QPoll/ctrl/qp_mixedquestion.php
@@ -5,7 +5,7 @@
66 }
77
88 /**
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
1010 * declaration/voting mode (UI input/output)
1111 */
1212 class qp_MixedQuestion extends qp_TabularQuestion {
@@ -15,7 +15,6 @@
1616 * also may be altered during the poll generation
1717 */
1818 function parseBody() {
19 - # Parameters used in some special cases.
2019 $this->mProposalPattern = '`^';
2120 foreach ( $this->mCategories as $catDesc ) {
2221 $this->mProposalPattern .= '(\[\]|\(\)|<>)';
Index: trunk/extensions/QPoll/ctrl/qp_tabularquestion.php
@@ -5,19 +5,29 @@
66 }
77
88 /**
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
1010 * declaration/voting mode (UI input/output)
1111 */
1212 class qp_TabularQuestion extends qp_StubQuestion {
1313
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+ */
2232 function parseBodyHeader( $input ) {
2333 $this->raws = preg_split( '`\n`su', $input, -1, PREG_SPLIT_NO_EMPTY );
2434 $categorySpans = false;
@@ -119,7 +129,7 @@
120130 * @param $input the raw source of category spans
121131 */
122132 # 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
124134 function parseCategorySpans( $input ) {
125135 $row = array();
126136 if ( $this->mType != 'singleChoice' ) {
@@ -257,7 +267,6 @@
258268 * also may be altered during the poll generation
259269 */
260270 function questionParseBody( $inputType ) {
261 - # Parameters used in some special cases.
262271 $proposalId = -1;
263272 foreach ( $this->raws as $raw ) {
264273 if ( !preg_match( $this->mProposalPattern, $raw, $matches ) ) {
Index: trunk/extensions/QPoll/ctrl/qp_stubquestion.php
@@ -19,6 +19,17 @@
2020 var $mPrevProposalCategoryId = Array(); // user true/false answers to the question's proposal from DB
2121 var $mPrevProposalCategoryText = Array(); // user text answers to the question's proposal from DB
2222
 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+
2334 # load some question fields from qp_QuestionData given
2435 # (usually qp_QuestionData is an array property of qp_PollStore instance)
2536 # @param $qdata - an instance of qp_QuestionData
@@ -80,7 +91,7 @@
8192 # @param the object of type qp_PollStore
8293 function store( qp_PollStore &$pollStore ) {
8394 if ( $pollStore->pid !== null ) {
84 - $pollStore->Questions[ $this->mQuestionId ] = new qp_QuestionData( array(
 95+ $pollStore->Questions[ $this->mQuestionId ] = qp_PollStore::newQuestionData( array(
8596 'from' => 'postdata',
8697 'type' => $this->mType,
8798 'common_question' => $this->mCommonQuestion,
Index: trunk/extensions/QPoll/ctrl/qp_poll.php
@@ -380,7 +380,7 @@
381381 if ( $error_msg !== '' ) {
382382 $question = new qp_StubQuestion(
383383 $this,
384 - qp_QuestionView::newFromBaseView( $this->view ),
 384+ qp_StubQuestionView::newFromBaseView( $this->view ),
385385 ++$this->mQuestionId
386386 );
387387 $question->setState( 'error', $error_msg );
@@ -388,9 +388,9 @@
389389 }
390390
391391 $qt = qp_Setup::$questionTypes[$type];
392 - $question = new $qt['className'](
 392+ $question = new $qt['ctrl'](
393393 $this,
394 - qp_QuestionView::newFromBaseView( $this->view ),
 394+ call_user_func( array( $qt['view'], 'newFromBaseView' ), $this->view ),
395395 ++$this->mQuestionId
396396 );
397397 # set the question type and subtype corresponding to the header 'type' attribute
@@ -435,7 +435,7 @@
436436 /**
437437 * Populates the question with data and builds question->view
438438 */
439 - function parseQuestionBody( qp_AbstractQuestion $question ) {
 439+ function parseQuestionBody( qp_StubQuestion $question ) {
440440 if ( $question->getState() == 'error' ) {
441441 # error occured during the previously performed header parsing, do not process further
442442 $question->view->addHeaderError();
Index: trunk/extensions/QPoll/ctrl/qp_questionstats.php
@@ -4,7 +4,8 @@
55 die( "This file is part of the QPoll extension. It is not a valid entry point.\n" );
66 }
77
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)
910 */
1011 class qp_QuestionStats extends qp_AbstractQuestion {
1112
@@ -14,7 +15,7 @@
1516 * @param $poll an instance of question's parent controller
1617 * @param $view an instance of question view "linked" to this question
1718 * @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
1920 */
2021 function __construct( qp_PollStats $poll, qp_QuestionStatsView $view, $type, $questionId ) {
2122 parent::__construct( $poll, $view, $questionId );
Index: trunk/extensions/QPoll/qp_user.php
@@ -138,21 +138,30 @@
139139
140140 static $questionTypes = array(
141141 'mixed' => array(
142 - 'className' => 'qp_MixedQuestion',
 142+ 'ctrl' => 'qp_MixedQuestion',
 143+ 'view' => 'qp_TabularQuestionView',
143144 'mType' => 'mixedChoice'
144145 ),
145146 'unique()' => array(
146 - 'className' => 'qp_TabularQuestion',
 147+ 'ctrl' => 'qp_TabularQuestion',
 148+ 'view' => 'qp_TabularQuestionView',
147149 'mType' => 'singleChoice',
148150 'mSubType' => 'unique'
149151 ),
150152 '()' => array(
151 - 'className' => 'qp_TabularQuestion',
 153+ 'ctrl' => 'qp_TabularQuestion',
 154+ 'view' => 'qp_TabularQuestionView',
152155 'mType' => 'singleChoice'
153156 ),
154157 '[]' => array(
155 - 'className' => 'qp_TabularQuestion',
 158+ 'ctrl' => 'qp_TabularQuestion',
 159+ 'view' => 'qp_TabularQuestionView',
156160 'mType' => 'multipleChoice'
 161+ ),
 162+ 'text' => array(
 163+ 'ctrl' => 'qp_TextQuestion',
 164+ 'view' => 'qp_TextQuestionView',
 165+ 'mType' => 'textQuestion'
157166 )
158167 );
159168
@@ -276,6 +285,7 @@
277286 'ctrl/qp_stubquestion.php' => 'qp_StubQuestion',
278287 'ctrl/qp_tabularquestion.php' => 'qp_TabularQuestion',
279288 'ctrl/qp_mixedquestion.php' => 'qp_MixedQuestion',
 289+ 'ctrl/qp_textquestion.php' => 'qp_TextQuestion',
280290 'ctrl/qp_questionstats.php' => 'qp_QuestionStats',
281291
282292 ## views are derived from single generic class
@@ -283,15 +293,22 @@
284294 # generic
285295 'view/qp_abstractview.php' => 'qp_AbstractView',
286296 # 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',
288301 'view/qp_questionstatsview.php' => 'qp_QuestionStatsView',
289302 # polls
290303 'view/qp_pollview.php' => 'qp_PollView',
291304 'view/qp_pollstatsview.php' => 'qp_PollStatsView',
292305
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' ),
295308
 309+ # question storage and page result question views
 310+ # (combined question storage & view)
 311+ 'qp_questiondata.php' => array( 'qp_QuestionData', 'qp_TextQuestionData' ),
 312+
296313 # results page
297314 'qp_results.php' => array( 'qp_SpecialPage', 'qp_QueryPage', 'PollResults' ),
298315
@@ -565,7 +582,7 @@
566583 $out[0][] = array( '__tag' => 'div', 'class' => 'script_view', qp_Setup::specialchars( $input ) . "\n" );
567584 $markercount = count( self::$markerList );
568585 $marker = "!qpoll-script-view{$markercount}-qpoll!";
569 - self::$markerList[$markercount] = qp_Renderer::renderHTMLobject( $out );
 586+ self::$markerList[$markercount] = qp_Renderer::renderTagArray( $out );
570587 return $marker;
571588 }
572589
@@ -601,10 +618,26 @@
602619
603620 /* renders output data */
604621 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 ) {
609642 $tag_open = "";
610643 $tag_close = "";
611644 $tag_val = null;
@@ -612,20 +645,23 @@
613646 ksort( $tag );
614647 if ( array_key_exists( '__tag', $tag ) ) {
615648 # list inside of tag
616 - $tag_open .= "<" . $tag[ '__tag' ];
 649+ $tag_open .= "<" . $tag['__tag'];
617650 foreach ( $tag as $attr_key => &$attr_val ) {
618651 if ( is_int( $attr_key ) ) {
619652 if ( $tag_val === null )
620653 $tag_val = "";
621654 if ( is_array( $attr_val ) ) {
622655 # recursive tags
623 - $tag_val .= self::renderHTMLobject( $attr_val );
 656+ $tag_val .= self::renderTagArray( $attr_val );
624657 } else {
625658 # text
626659 $tag_val .= $attr_val;
627660 }
628661 } else {
629662 # 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+ }
630666 if ( substr( $attr_key, 0, 2 ) != "__" ) {
631667 # include only non-reserved attributes
632668 $tag_open .= " $attr_key=\"" . $attr_val . "\"";
@@ -648,17 +684,13 @@
649685 if ( is_int( $attr_key ) ) {
650686 if ( is_array( $attr_val ) ) {
651687 # recursive tags
652 - $tag_val .= self::renderHTMLobject( $attr_val );
 688+ $tag_val .= self::renderTagArray( $attr_val );
653689 } else {
654690 # text
655691 $tag_val .= $attr_val;
656692 }
657693 } 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 );
663695 }
664696 }
665697 }
@@ -670,7 +702,7 @@
671703 }
672704
673705 /**
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
675707 */
676708 static function addClass( &$tag, $className ) {
677709 if ( !isset( $tag['class'] ) ) {
@@ -682,9 +714,15 @@
683715 }
684716 }
685717
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+ */
689727 static function newRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
690728 $result = "";
691729 if ( count( $row ) > 0 ) {
@@ -692,8 +730,8 @@
693731 if ( !is_array( $cell ) ) {
694732 $cell = array( 0 => $cell );
695733 }
696 - $cell[ '__tag' ] = $celltag;
697 - $cell[ '__end' ] = "\n";
 734+ $cell['__tag'] = $celltag;
 735+ $cell['__end'] = "\n";
698736 if ( is_array( $attribute_maps ) ) {
699737 # converts ("count"=>3) to ("colspan"=>3) in table headers - don't use frequently
700738 foreach ( $attribute_maps as $key => $val ) {
@@ -714,12 +752,18 @@
715753 return $result;
716754 }
717755
718 - # add row to the table
 756+ /**
 757+ * Add row to the table
 758+ * todo: document
 759+ */
719760 static function addRow( &$table, $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
720761 $table[] = self::newRow( $row, $rowattrs, $celltag, $attribute_maps );
721762 }
722763
723 - # add column to the table
 764+ /**
 765+ * Add column to the table
 766+ * todo: document
 767+ */
724768 static function addColumn( &$table, $column, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
725769 if ( count( $column ) > 0 ) {
726770 $row = 0;
@@ -760,11 +804,13 @@
761805 static function displayRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
762806 # temporary var $tagsrow used to avoid warning in E_STRICT mode
763807 $tagsrow = self::newRow( $row, $rowattrs, $celltag, $attribute_maps );
764 - return self::renderHTMLobject( $tagsrow );
 808+ return self::renderTagArray( $tagsrow );
765809 }
766810
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+ */
769815 static function applyAttrsToRow( &$row, $attrs ) {
770816 if ( is_array( $attrs ) && count( $attrs > 0 ) ) {
771817 foreach ( $row as &$cell ) {
Index: trunk/extensions/QPoll/qp_pollstore.php
@@ -4,110 +4,6 @@
55 die( "This file is part of the QPoll extension. It is not a valid entry point.\n" );
66 }
77
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 -
1128 /**
1139 * An interpretation result of user answer to the quiz
11410 */
@@ -356,6 +252,23 @@
357253 }
358254 }
359255
 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+
360273 function getPollId() {
361274 return $this->mPollId;
362275 }
@@ -427,7 +340,7 @@
428341 $row->type = $typeFromVer0_5[$row->type];
429342 }
430343 # 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(
432345 'from' => 'qid',
433346 'qid' => $question_id,
434347 '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'] = '&#160;';
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 => '&#160;' ),
492 - 2 => array( '__tag' => 'div', 'class' => 'bar2', 'style' => &$this->cellTemplateParam['bar2style'], 0 => '&#160;' ),
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 => '&#160;' ),
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'] = '&#160;';
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'] = '&#160;';
 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 => '&#160;' ),
 391+ 2 => array( '__tag' => 'div', 'class' => 'bar2', 'style' => &$this->cellTemplateParam['bar2style'], 0 => '&#160;' ),
 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 => '&#160;' ),
 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'] = '&#160;';
 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
1448 + native
Index: trunk/extensions/QPoll/view/qp_pollstatsview.php
@@ -90,9 +90,9 @@
9191 }
9292 if ( $this->perRow > 1 ) {
9393 $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 );
9595 } else {
96 - return qp_Renderer::renderHTMLobject( $write_row );
 96+ return qp_Renderer::renderTagArray( $write_row );
9797 }
9898 }
9999
@@ -103,7 +103,7 @@
104104 function renderPoll() {
105105 # Generates the output.
106106 $qpoll_div = array( '__tag' => 'div', 'class' => 'qpoll', 0 => $this->renderQuestionViews() );
107 - return qp_Renderer::renderHTMLobject( $qpoll_div );
 107+ return qp_Renderer::renderTagArray( $qpoll_div );
108108 }
109109
110110 } /* 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
1220 + native
Index: trunk/extensions/QPoll/view/qp_pollview.php
@@ -88,9 +88,9 @@
8989 }
9090 if ( $this->perRow > 1 ) {
9191 $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 );
9393 } else {
94 - return qp_Renderer::renderHTMLobject( $write_row );
 94+ return qp_Renderer::renderTagArray( $write_row );
9595 }
9696 }
9797
@@ -112,7 +112,7 @@
113113 $qpoll_div[] = array( '__tag' => 'div', 'class' => 'interp_answer', qp_Setup::specialchars( $longAnswer ) );
114114 }
115115 # 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" );
117117 $qpoll_div[] = &$qpoll_form;
118118 # Determine the content of the settings table.
119119 $settings = Array();
@@ -163,7 +163,7 @@
164164 }
165165
166166 $qpoll_form[] = &$p;
167 - return qp_Renderer::renderHTMLobject( $qpoll_div );
 167+ return qp_Renderer::renderTagArray( $qpoll_div );
168168 }
169169
170170 } /* end of qp_PollView class */
Index: trunk/extensions/QPoll/view/qp_questionstatsview.php
@@ -42,10 +42,10 @@
4343 * allows to modify these for quizes results at the later stage (see qp_poll.php)
4444 * todo: transfer view logic completely from controllers
4545 */
46 -class qp_QuestionStatsView extends qp_QuestionView {
 46+class qp_QuestionStatsView extends qp_TabularQuestionView {
4747
4848 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 );
5050 }
5151
5252 function isCompatibleController( $ctrl ) {
@@ -96,7 +96,7 @@
9797 $this->cellTemplateParam['percents'] = $percents . '%';
9898 # template has to be rendered immediately, because $this->cellTemplateParam[] are used as pointers and thus,
9999 # will always be overwritten
100 - return QP_Renderer::renderHTMLobject( $this->cellTemplate );
 100+ return QP_Renderer::renderTagArray( $this->cellTemplate );
101101 } else {
102102 return '';
103103 }
@@ -141,7 +141,7 @@
142142 $this->cellTemplateParam['percents'] = $percents . '%';
143143 $this->cellTemplateParam['bar1style'] = 'width:' . $percents . 'px;' . $this->cellTemplate[ 'bar1showres' ];
144144 $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'] );
146146 } else {
147147 return '';
148148 }
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
1275 + native

Status & tagging log