http://www.mediawiki.org/wiki/Special:Code/MediaWiki/99779
Revision: 99779
Author: questpc
Date: 2011-10-14 16:05:28 +0000 (Fri, 14 Oct 2011)
Log Message:
-----------
Text question layout allows to display proposal parts / categories either
inline or in table cells. In last case, text question optionally can be
transposed. Fixed and explained last message from recent commits.
Modified Paths:
--------------
trunk/extensions/QPoll/ctrl/poll/qp_abstractpoll.php
trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
trunk/extensions/QPoll/i18n/qp.i18n.php
trunk/extensions/QPoll/qp_user.php
trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
trunk/extensions/QPoll/view/question/qp_textquestionview.php
Modified: trunk/extensions/QPoll/ctrl/poll/qp_abstractpoll.php
===================================================================
--- trunk/extensions/QPoll/ctrl/poll/qp_abstractpoll.php 2011-10-14
16:01:48 UTC (rev 99778)
+++ trunk/extensions/QPoll/ctrl/poll/qp_abstractpoll.php 2011-10-14
16:05:28 UTC (rev 99779)
@@ -75,6 +75,13 @@
var $pollStore = null;
/**
+ * possible xml-like attributes the question may have
+ */
+ var $questionAttributeKeys = array(
+ 't[yi]p[eo]', 'layout', 'textwidth', 'propwidth', 'showresults'
+ );
+
+ /**
* default values of 'propwidth', 'textwidth' and 'layout' attributes
* will be applied to child questions that do not have these attributes
defined
*
@@ -232,12 +239,7 @@
* @return string the value of question's type attribute
*/
function getQuestionAttributes( $attr_str, &$paramkeys ) {
- $paramkeys = array( 't[yi]p[eo]' => null, 'layout' => null,
'textwidth' => null, 'propwidth' => null, 'showresults' => null );
- $match = array();
- foreach ( $paramkeys as $key => $val ) {
- preg_match( '`' . $key . '\s?=\s?"(.*?)"`u', $attr_str,
$match );
- $paramkeys[$key] = ( count( $match ) > 1 ) ? $match[1]
: null;
- }
+ $paramkeys = qp_Setup::getXmlLikeAttributes( $attr_str,
$this->questionAttributeKeys );
# apply default questions attributes from poll definition, if
there is any
foreach ( $this->defaultQuestionAttributes as $attr => $val ) {
if ( $paramkeys[$attr] === null ) {
Modified: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
===================================================================
--- trunk/extensions/QPoll/ctrl/question/qp_textquestion.php 2011-10-14
16:01:48 UTC (rev 99778)
+++ trunk/extensions/QPoll/ctrl/question/qp_textquestion.php 2011-10-14
16:05:28 UTC (rev 99779)
@@ -6,22 +6,41 @@
/**
* Stores the list of current category options -
- * usually the pipe-separated entries in double angle brackets list
+ * usually the pipe-separated entries in specified brackets list
*/
class qp_TextQuestionOptions {
# boolean, indicates whether incoming tokens are category list elements
var $isCatDef;
+ # type of created element (text,radio,checkbox)
+ var $type;
# counter of pipe-separated elements in-between << >> markup
- # used to distinguish real category options from textwidth definition
+ # used to distinguish real category options from attributes definition
+ # for type='text'
var $catDefIdx;
# list of input options; array whose every element is a string
var $input_options;
- # a value of textwidth definition for input text field
- # it is defined as first element of options list, for example:
- # <<::12>> or <<::15|test>>
- # currently, it is used only for text inputs (not for select/option
list)
- var $textwidth;
+
+ # whether the current option has xml-like attributes specified
+ var $hasAttributes = false;
+ var $attributes = array(
+ ## a value of input text field width in 'em'
+ # possible values: null, positive int
+ # defined as first element xml-like attribute of options list,
for example:
+ # <<:: width="12">> or <<:: width="15"|test>>
+ # currently, it is used only for text inputs (not for
select/option list)
+ 'width' => null,
+ ## whether the text options of current category has to be
sorted;
+ # possible values: null (do not sort), 'asc', 'desc'
+ # defined as first element xml-like attribute of options list,
for example:
+ # <<:: sorting="desc"|a|b|c>>
+ 'sorting' => null,
+ ## whether the checkbox type option of current category has to
be checked by default;
+ # possible value: null (not checked), not null (checked)
+ # defined as first element xml-like attribute of options list,
for example:
+ # <[checked=""]>
+ 'checked' => null
+ );
# a pointer to last element in $this->input_options array
var $iopt_last;
@@ -39,10 +58,15 @@
* Applies default settings to the options list
* New category begins
*/
- function startOptionsList() {
+ function startOptionsList( $type ) {
$this->isCatDef = true;
+ $this->type = $type;
$this->input_options = array( 0 => '' );
- $this->textwidth = null; // will use default value
+ $this->hasAttributes = false;
+ # set default values of xml-like attributes
+ foreach ( $this->attributes as $attr_name => &$attr_val ) {
+ $attr_val = null;
+ }
$this->iopt_last = &$this->input_options[0];
}
@@ -51,33 +75,54 @@
* This option will be "current last option"
*/
function addEmptyOption() {
- # add new empty option only if there was no textwidth definition
- if ( is_null( $this->textwidth ) || $this->catDefIdx !== 0 ) {
- # add new empty option to the end of the list
- $this->input_options[] = '';
- $this->iopt_last = &$this->input_options[count(
$this->input_options ) - 1];
+ # new options are meaningful only for type 'text'
+ if ( $this->type === 'text' ) {
+ # add new empty option only if there was no xml
attributes definition
+ if ( !$this->hasAttributes || $this->catDefIdx !== 0 ) {
+ # add new empty option to the end of the list
+ $this->input_options[] = '';
+ $this->iopt_last = &$this->input_options[count(
$this->input_options ) - 1];
+ }
+ $this->catDefIdx++;
}
- $this->catDefIdx++;
}
/**
- * Set string value to current last option
+ * Add string part to value of current last option
* @param $token string current value of token between pipe separators
- * Also, _optionally_ overrides textwidth property
+ * Also, _optionally_ parses xml-like attributes (when these are found
in category definition)
*/
function addToLastOption( $token ) {
- # first entry of category options might be definition of
- # the current category input textwidth instead
$matches = array();
- if ( count( $this->input_options ) === 1 &&
- preg_match( '`^\s*::(\d{1,2})\s*$`', $token,
$matches ) &&
- $matches[1] > 0 ) {
- # override the textwidth of input options
- $this->textwidth = intval( $matches[1] );
- } else {
- # add new input option
- $this->iopt_last .= $token;
+ if ( $this->type === 'text' ) {
+ # first entry of "category type text" might contain
current category
+ # xml-like attributes
+ if ( count( $this->input_options ) === 1 &&
+ preg_match( '`^::\s*(.+)$`', $token, $matches )
) {
+ # note that hasAttributes is always true
regardless the attributes are used or not,
+ # because it is checked in
$this->addEmptyOption()
+ $this->hasAttributes = true;
+ # parse attributes string
+ $option_attributes =
qp_Setup::getXmlLikeAttributes( $matches[1], array( 'width', 'sorting' ) );
+ # apply attributes to current option
+ foreach ( $option_attributes as $attr_name =>
$attr_val ) {
+ $this->attributes[$attr_name] =
$attr_val;
+ }
+ return;
+ }
+ } elseif ( $this->type === 'checkbox' ) {
+ if ( $token !== '' ) {
+ # checkbox type of categories do not contain
text values,
+ # only xml-like attributes
+ $option_attributes =
qp_Setup::getXmlLikeAttributes( $token, array( 'checked' ) );
+ # apply attributes to current option
+ foreach ( $option_attributes as $attr_name =>
$attr_val ) {
+ $this->attributes[$attr_name] =
$attr_val;
+ }
+ }
}
+ # add new input option
+ $this->iopt_last .= $token;
}
/**
@@ -92,6 +137,14 @@
# make sure unique elements keys are consequitive
starting from 0
$this->input_options[] = $option;
}
+ switch ( $this->attributes['sorting'] ) {
+ case 'asc' :
+ sort( $this->input_options, SORT_STRING );
+ break;
+ case 'desc' :
+ rsort( $this->input_options, SORT_STRING );
+ break;
+ }
}
} /* end of qp_TextQuestionOptions class */
@@ -105,7 +158,8 @@
*/
class qp_TextQuestion extends qp_StubQuestion {
- const PROP_CAT_PATTERN = '`(<<|>>|{{|}}|\[\[|\]\]|\|)`u';
+ # regexp for separation of proposal line tokens
+ static $propCatPattern = null;
# $propview is an instance of qp_TextQuestionProposalView
# which contains parsed tokens for combined
@@ -117,7 +171,48 @@
# only proposal parts and category options
var $dbtokens = array();
+ # list of opening input braces types
+ static $input_braces_types = array(
+ '<<' => 'text',
+ '<(' => 'radio',
+ '<[' => 'checkbox'
+ );
+ # matches of opening / closing braces
+ static $matching_braces = array(
+ # wiki link
+ '[[' => ']]',
+ # wiki magicword
+ '{{' => '}}',
+ # text input / select option
+ '<<' => '>>',
+ # radiobutton
+ '<(' => ')>',
+ # checkbox
+ '<[' => ']>'
+ );
+
/**
+ * Constructor
+ * @public
+ * @param $poll an instance of question's parent controller
+ * @param $view an instance of question view "linked" to
this question
+ * @param $questionId the identifier of the question used to
generate input names
+ */
+ function __construct( qp_AbstractPoll $poll, qp_StubQuestionView $view,
$questionId ) {
+ parent::__construct( $poll, $view, $questionId );
+ if ( self::$propCatPattern === null ) {
+ $braces_list = array_map( 'preg_quote',
+ array_merge(
+ ( array_values( self::$matching_braces
) ),
+ array_keys( self::$matching_braces ),
+ array( '|' )
+ )
+ );
+ self::$propCatPattern = '/(' . implode( '|',
$braces_list ) . ')/u';
+ }
+ }
+
+ /**
* Parses question body header.
* Text questions do not have "body header" (no definitions of spans
and categories)
* so, this method just splits raw lines of body text to analyze raws
in $this->parseBody()
@@ -167,11 +262,6 @@
* also may be altered during the poll generation
*/
function parseBody() {
- $matching_braces = array(
- '[[' => ']]',
- '{{' => '}}',
- '<<' => '>>'
- );
$proposalId = 0;
# Currently, we use just a single instance (no nested
categories)
$opt = new qp_TextQuestionOptions();
@@ -185,63 +275,70 @@
$this->dbtokens = $brace_stack = array();
$catId = 0;
$last_brace = '';
- $tokens = preg_split( self::PROP_CAT_PATTERN, $raw, -1,
PREG_SPLIT_DELIM_CAPTURE );
+ $tokens = preg_split( self::$propCatPattern, $raw, -1,
PREG_SPLIT_DELIM_CAPTURE );
+ $matching_closed_brace = '';
foreach ( $tokens as $token ) {
- $isContinue = false;
- switch ( $token ) {
- case '|' :
- if ( $opt->isCatDef ) {
- if ( count( $brace_stack ) == 1
&& $brace_stack[0] === '>>' ) {
- # pipe char starts new
option only at top brace level,
- # with angled braces
- $opt->addEmptyOption();
- $isContinue = true;
+ try {
+ # $toBeStored == true when current
$token has to be stored into
+ # category / proposal list (depending
on $opt->isCatDef)
+ $toBeStored = true;
+ if ( $token === '|' ) {
+ # parameters separator
+ if ( $opt->isCatDef ) {
+ if ( count(
$brace_stack ) == 1 && $brace_stack[0] === $matching_closed_brace ) {
+ # pipe char
starts new option only at top brace level,
+ # with matching
input brace
+
$opt->addEmptyOption();
+ $toBeStored =
false;
+ }
}
- }
- break;
- case '[[' :
- case '{{' :
- case '<<' :
- array_push( $brace_stack,
$matching_braces[$token] );
- if ( $token === '<<' && count(
$brace_stack ) == 1 ) {
- $opt->startOptionsList();
- $isContinue = true;
- }
- break;
- case ']]' :
- case '}}' :
- case '>>' :
- if ( count( $brace_stack ) > 0 ) {
- $last_brace = array_pop(
$brace_stack );
- if ( $last_brace != $token ) {
- array_push(
$brace_stack, $last_brace );
- break;
+ } elseif ( array_key_exists( $token,
self::$matching_braces ) ) {
+ # opening braces
+ array_push( $brace_stack,
self::$matching_braces[$token] );
+ if ( array_key_exists( $token,
self::$input_braces_types ) &&
+ count(
$brace_stack ) == 1 ) {
+ # start category
definiton
+ $matching_closed_brace
= self::$matching_braces[$token];
+ $opt->startOptionsList(
self::$input_braces_types[$token] );
+ $toBeStored = false;
}
- if ( count( $brace_stack ) > 0
|| $token !== '>>' ) {
- break;
+ } elseif ( in_array( $token,
self::$matching_braces ) ) {
+ # closing braces
+ if ( count( $brace_stack ) > 0
) {
+ $last_brace =
array_pop( $brace_stack );
+ if ( $last_brace !=
$token ) {
+ array_push(
$brace_stack, $last_brace );
+ throw new
Exception( 'break' );
+ }
+ if ( count(
$brace_stack ) > 0 || $token !== $matching_closed_brace ) {
+ throw new
Exception( 'break' );
+ }
+ $matching_closed_brace
= '';
+ # add new category
input options for the storage
+ $this->dbtokens[] =
$opt->input_options;
+ # setup mCategories
+
$this->mCategories[$catId] = array( 'name' => strval( $catId ) );
+ # load
proposal/category answer (when available)
+
$this->loadProposalCategory( $opt, $proposalId, $catId );
+ # current category is
over
+ $catId++;
+ $toBeStored = false;
}
- # add new category input
options for the storage
- $this->dbtokens[] =
$opt->input_options;
- # setup mCategories
- $this->mCategories[$catId] =
array( 'name' => strval( $catId ) );
- # load proposal/category answer
(when available)
- $this->loadProposalCategory(
$opt, $proposalId, $catId );
- # current category is over
- $catId++;
- $isContinue = true;
}
- break;
+ } catch ( Exception $e ) {
+ if ( $e->getMessage() !== 'break' ) {
+ throw new MWException(
$e->getMessage() );
+ }
}
- if ( $isContinue ) {
- continue;
+ if ( $toBeStored ) {
+ if ( $opt->isCatDef ) {
+ $opt->addToLastOption( $token );
+ } else {
+ # add new proposal part
+ $this->dbtokens[] = strval(
$token );
+
$this->propview->addProposalPart( $token );
+ }
}
- if ( $opt->isCatDef ) {
- $opt->addToLastOption( $token );
- } else {
- # add new proposal part
- $this->dbtokens[] = strval( $token );
- $this->propview->addProposalPart(
$token );
- }
}
# check if there is at least one category defined
if ( $catId === 0 ) {
Modified: trunk/extensions/QPoll/i18n/qp.i18n.php
===================================================================
--- trunk/extensions/QPoll/i18n/qp.i18n.php 2011-10-14 16:01:48 UTC (rev
99778)
+++ trunk/extensions/QPoll/i18n/qp.i18n.php 2011-10-14 16:05:28 UTC (rev
99779)
@@ -126,7 +126,7 @@
'qp_error_no_answer' => 'Unanswered proposal.',
'qp_error_unique' => 'Question of type unique() has more proposals than
possible answers defined: Impossible to complete.',
'qp_error_no_more_attempts' => 'You have reached maximal number of
submitting attempts for this poll.',
- 'qp_error_no_interpretation' => 'Interpretation script does not exist',
+ 'qp_error_no_interpretation' => 'Interpretation script does not exist.',
'qp_error_interpretation_no_return' => 'Interpretation script returned
no result.',
'qp_error_structured_interpretation_is_too_long' => 'Structured
interpretation is too long to be stored in database. Please correct your
interpretation script.',
'qp_error_no_json_decode' => 'Interpretation of poll answers requires
json_decode() PHP function.',
@@ -190,6 +190,7 @@
* $3 is the poll ID of the poll, which this erroneous poll depends on.',
'qp_error_too_many_spans' => 'There cannot be more category groups
defined than the total count of subcategories.',
'qp_error_too_few_spans' => 'Every category group should include at
least two subcategories',
+ 'qp_error_no_interpretation' => 'Title of interpretation script was
specified in poll header, however no article was found with that title. Either
remove "interpretation" xml attribute of poll or create the title specified by
"interpretation" attribute.',
'qp_error_interpretation_no_return' => 'Interpretation script missed an
return statement.',
'qp_error_structured_interpretation_is_too_long' => "Structured
interpretation is serialized string containing scalar value or an associative
array stored into database table field. It's purpose is to have measurable,
easily processable interpretation result for the particular poll which then can
be processed by external tools (via XLS export) or, to be read and processed by
next poll interpretation script (data import and in the future maybe an export
as well). When the serialized string is too long, it should never be stored,
otherwise it will be truncated by DBMS so it cannot be properly unserialized
later.",
'qp_error_eval_missed_lang_attr' => '{{doc-important|Do not translate
"lang" as it is the name of an XML attribute that is not localised.}}',
@@ -1132,7 +1133,7 @@
'qp_error_no_answer' => 'Proposition sans réponse',
'qp_error_unique' => 'La question de type unique() a plus de
propositions qu’il n’y a de réponses possibles définies : impossible de
compléter',
'qp_error_no_more_attempts' => 'Vous avez atteint le nombre maximal de
tentatives de soumission pour ce sondage.',
- 'qp_error_no_interpretation' => "Le script d'interprétation n'existe
pas",
+ 'qp_error_no_interpretation' => "Le script d'interprétation n'existe
pas.",
'qp_error_interpretation_no_return' => "Le script d'interprétation n'a
renvoyé aucun résultat.",
'qp_error_structured_interpretation_is_too_long' => "L'interprétation
structurée est trop longue pour être stockée dans la base de données. Merci de
corriger votre script d'interprétation.",
'qp_error_no_json_decode' => "L'interprétation des réponses au sondage
nécessite la fonction PHP json_decode().",
@@ -1278,7 +1279,7 @@
'qp_error_no_answer' => 'Proposta sen resposta',
'qp_error_unique' => 'A pregunta de tipo unique() ten definidas máis
propostas que respostas posibles: imposible de completar',
'qp_error_no_more_attempts' => 'Alcanzou o número máximo de intentos de
envío para esta enquisa.',
- 'qp_error_no_interpretation' => 'A escritura de interpretación non
existe',
+ 'qp_error_no_interpretation' => 'A escritura de interpretación non
existe.',
'qp_error_interpretation_no_return' => 'A escritura de interpretación
non devolveu resultados.',
'qp_error_structured_interpretation_is_too_long' => 'A interpretación
estruturada é longa de máis para almacenala na base de datos. Corrixa a súa
escritura de interpretación.',
'qp_error_no_json_decode' => 'A interpretación das respostas ás
enquisas necesitan a función PHP json_decode().',
@@ -1659,7 +1660,7 @@
'qp_error_no_answer' => 'Proposition sin responsa',
'qp_error_unique' => 'Pro le question de typo unique() es definite plus
propositiones que responsas possibile: non pote completar',
'qp_error_no_more_attempts' => 'Tu ha attingite le numero maxime de
tentativas de submission pro iste sondage',
- 'qp_error_no_interpretation' => 'Le script de interpretation non
existe',
+ 'qp_error_no_interpretation' => 'Le script de interpretation non
existe.',
'qp_error_interpretation_no_return' => 'Le script de interpretation non
retornava resultatos',
'qp_error_structured_interpretation_is_too_long' => 'Le interpretation
structurate es troppo longe pro immagazinar lo in le base de datos. Per favor
corrige le script de interpretation.',
'qp_error_no_json_decode' => 'Le interpretation del responsas al
sondage require le function PHP json_decode()',
Modified: trunk/extensions/QPoll/qp_user.php
===================================================================
--- trunk/extensions/QPoll/qp_user.php 2011-10-14 16:01:48 UTC (rev 99778)
+++ trunk/extensions/QPoll/qp_user.php 2011-10-14 16:05:28 UTC (rev 99779)
@@ -444,6 +444,23 @@
return $username;
}
+ /**
+ * Parse string with XML-like attributes (no tag, only attributes)
+ * @param $attr_str attribute string
+ * @param $attr_list list of XML attributes, PCRE allowed
+ * @return array key is attribute regexp
+ * value is the value of attribute or null
+ */
+ static function getXmlLikeAttributes( $attr_str, $attr_list ) {
+ $attr_vals = array();
+ $match = array();
+ foreach ( $attr_list as $attr_name ) {
+ preg_match( '/' . $attr_name . '\s?=\s?"(.*?)"/u',
$attr_str, $match );
+ $attr_vals[$attr_name] = ( count( $match ) > 1 ) ?
$match[1] : null;
+ }
+ return $attr_vals;
+ }
+
static function onLoadAllMessages() {
if ( !self::$messagesLoaded ) {
self::$messagesLoaded = true;
Modified: trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
===================================================================
--- trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
2011-10-14 16:01:48 UTC (rev 99778)
+++ trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
2011-10-14 16:05:28 UTC (rev 99779)
@@ -16,15 +16,25 @@
# property 'options' indicates current category options list
# property 'error' indicates error message
var $viewtokens = array();
+ var $lastTokenType = '';
/**
* Add new proposal part (between two categories or line bounds)
* It is just an element of string type
*
- * @param $prop string proposal part
+ * @param $token string proposal part
*/
- function addProposalPart( $prop ) {
- $this->viewtokens[] = $prop;
+ function addProposalPart( $token ) {
+ if ( $this->lastTokenType === 'proposal' ) {
+ # add to already existing proposal part
+ $last_prop = array_pop( $this->viewtokens );
+ $last_prop .= $token;
+ array_push( $this->viewtokens, $last_prop );
+ return;
+ }
+ # start new proposal part
+ $this->viewtokens[] = $token;
+ $this->lastTokenType = 'proposal';
}
/**
@@ -39,27 +49,26 @@
*/
function addCatDef( qp_TextQuestionOptions $opt, $name, $text_answer,
$unanswered ) {
# $catdef instanceof stdClass properties:
+ # property 'type' contains type of current category: 'text',
'checkbox', 'radio'
# property 'options' stores an array of user options
# Multiple options will be selected from the list
# Single option will be displayed as text input
# property 'name' contains name of input element
# property 'value' contains value previousely chosen
# by user (if any)
- # property 'textwidth' may optionally override default
- # text input width
+ # property 'attributes' contain extra atttibutes of current
category definition
# property 'unanswered' boolean
# true - the question was POSTed but category is
unanswered
# false - the question was not POSTed or category is
answered
- $catdef = (object) array(
+ $this->viewtokens[] = (object) array(
+ 'type' => $opt->type,
'options' => $opt->input_options,
'name' => $name,
'value' => $text_answer,
- 'unanswered' => $unanswered
+ 'unanswered' => $unanswered,
+ 'attributes' => $opt->attributes
);
- if ( !is_null( $opt->textwidth ) ) {
- $catdef->textwidth = $opt->textwidth;
- }
- $this->viewtokens[] = $catdef;
+ $this->lastTokenType = 'category';
}
/**
@@ -78,6 +87,9 @@
if ( $errmsg !== '' ) {
array_unshift( $this->viewtokens, (object) array(
'error'=> $errmsg ) );
}
+ if ( count( $this->viewtokens ) < 2 ) {
+ $this->lastTokenType = 'errmsg';
+ }
}
/**
Modified: trunk/extensions/QPoll/view/question/qp_textquestionview.php
===================================================================
--- trunk/extensions/QPoll/view/question/qp_textquestionview.php
2011-10-14 16:01:48 UTC (rev 99778)
+++ trunk/extensions/QPoll/view/question/qp_textquestionview.php
2011-10-14 16:05:28 UTC (rev 99779)
@@ -38,6 +38,44 @@
}
/**
+ * Proposal / category view row building helper.
+ * Currently the single instance is re-used (no nesting).
+ */
+class qp_TextQuestionViewRow {
+
+ # each element of row is real table cell or "cell" with spans,
+ # depending on $this->tabularDisplay value
+ var $row;
+ # tagarray with error elements will be merged into adjascent cells
+ var $error;
+ # tagarray with current cell builded for row
+ # cell contains one or multiple tags, describing proposal part or
category
+ var $cell;
+
+ function __construct() {
+ $this->reset();
+ }
+
+ function reset() {
+ $this->row = array();
+ $this->error = array();
+ $this->cell = array();
+ }
+
+ function addCell() {
+ if ( count( $this->error ) > 0 ) {
+ # merge previous errors to current cell
+ $this->cell = array_merge( $this->error, $this->cell );
+ $this->error = array();
+ }
+ if ( count( $this->cell ) > 0 ) {
+ $this->row[] = $this->cell;
+ }
+ }
+
+} /* end of qp_TextQuestionViewRow class */
+
+/**
* Stores question proposals views (see qp_textqestion.php) and
* allows to modify these for results of quizes at the later stage (see
qp_poll.php)
* An attempt to make somewhat cleaner question view
@@ -45,7 +83,20 @@
*/
class qp_TextQuestionView extends qp_StubQuestionView {
+ ## the layout of question
+ # true: categories and proposal parts will be placed into
+ # table cells (display:table-cell)
+ # false: categories and proposal parts will be placed into
+ # spans (display:inline)
+ var $tabularDisplay = false;
+ # whether the resulting display table should be transposed
+ # meaningful only when $this->tabularDisplay is true
+ var $transposed = false;
+
+ # default style of text input
var $textInputStyle = '';
+ # view row
+ var $vr;
/**
* @param $parser
@@ -54,6 +105,7 @@
*/
function __construct( &$parser, &$frame, $showResults ) {
parent::__construct( $parser, $frame );
+ $this->vr = new qp_TextQuestionViewRow();
/* todo: implement showResults */
}
@@ -62,7 +114,10 @@
}
function setLayout( $layout, $textwidth ) {
- /* todo: implement vertical layout */
+ if ( $layout !== null ) {
+ $this->tabularDisplay = strpos( $layout, 'tabular' )
!== false;
+ $this->transposed = strpos( $layout, 'transpose' ) !==
false;
+ }
if ( $textwidth !== null ) {
$textwidth = intval( $textwidth );
if ( $textwidth > 0 ) {
@@ -127,8 +182,10 @@
* @return tagarray
*/
function renderParsedProposal( &$viewtokens ) {
- $row = array();
+ $vr = $this->vr;
+ $vr->reset();
foreach ( $viewtokens as $elem ) {
+ $vr->cell = array();
if ( is_object( $elem ) ) {
if ( isset( $elem->options ) ) {
$className = 'cat_part';
@@ -138,7 +195,7 @@
if ( isset( $elem->interpError ) ) {
$className = 'cat_noanswer';
# create view for
proposal/category error message
- $row[] = array(
+ $vr->cell[] = array(
'__tag' => 'span',
'class' =>
'proposalerror',
$elem->interpError
@@ -146,7 +203,7 @@
}
# create view for the input options part
if ( count( $elem->options ) === 1 ) {
- # one option produces html text
input
+ # one option produces html text
/ radio / checkbox input
$value = $elem->value;
# check, whether the definition
of category has "pre-filled" value
# single, non-unanswered,
non-empty option is a pre-filled value
@@ -158,19 +215,23 @@
$input = array(
'__tag' => 'input',
'class' => $className,
- 'type' => 'text',
+ 'type' => $elem->type,
'name' => $elem->name,
'value' =>
qp_Setup::specialchars( $value )
);
+ if (
$elem->attributes['checked'] !== null ) {
+ $input['checked'] =
'checked';
+ }
if ( $this->textInputStyle !=
'' ) {
# apply poll's
textwidth attribute
$input['style'] =
$this->textInputStyle;
}
- if ( isset( $elem->textwidth )
) {
- # apply current
category textwidth "option"
- $input['style'] =
'width:' . intval( $elem->textwidth ) . 'em;';
+ if ( $elem->attributes['width']
!== null ) {
+ # apply current
category width attribute
+ $input['style'] =
'width:' . intval( $elem->attributes['width'] ) . 'em;';
}
- $row[] = $input;
+ $vr->cell[] = $input;
+ $vr->addCell();
continue;
}
# multiple options produce html select
/ options
@@ -190,15 +251,16 @@
}
$html_options[] = $html_option;
}
- $row[] = array(
+ $vr->cell[] = array(
'__tag' => 'select',
'class' => $className,
'name' => $elem->name,
$html_options
);
+ $vr->addCell();
} elseif ( isset( $elem->error ) ) {
# create view for proposal/category
error message
- $row[] = array(
+ $vr->error[] = array(
'__tag' => 'span',
'class' => 'proposalerror',
$elem->error
@@ -208,14 +270,21 @@
}
} else {
# create view for the proposal part
- $row[] = array(
+ $vr->cell[] = array(
'__tag' => 'span',
'class' => 'prop_part',
$this->rtp( $elem )
);
+ $vr->addCell();
}
}
- return array( $row );
+ $vr->cell = array();
+ # make sure last "error" tokens are added, if any:
+ $vr->addCell();
+ if ( $this->tabularDisplay ) {
+ return $vr->row;
+ }
+ return array( $vr->row );
}
/**
@@ -239,7 +308,11 @@
foreach ( $this->pviews as &$propview ) {
$prop = $this->renderParsedProposal(
$propview->viewtokens );
$rowattrs = array( 'class' => $propview->rowClass );
- qp_Renderer::addRow( $questionTable, $prop, $rowattrs );
+ if ( $this->transposed ) {
+ qp_Renderer::addColumn( $questionTable, $prop,
$rowattrs );
+ } else {
+ qp_Renderer::addRow( $questionTable, $prop,
$rowattrs );
+ }
}
return $questionTable;
}
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs