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

Reply via email to