static function tagError( $msg, &$tag ) { ob_start(); var_dump( $tag ); $tagdump = ob_get_contents(); ob_end_clean(); # uncomment exception throwing for debugging purposes # throw new MWException( "<u>invalid argument: " . qp_Setup::specialchars( $msg ) . "</u> <pre>{$tagdump}</pre>" ); return "<u>invalid argument: " . qp_Setup::specialchars( $msg ) . "</u> <pre>{$tagdump}</pre>"; }
/** * Add interpretation results to tagarray of poll view */ function showInterpResults( array &$tagarray, qp_InterpResult $ctrl, $showDescriptions = false ) { if ( $ctrl->hasVisibleProperties() ) { return; } $interp = array(); if ( $showDescriptions ) { $interp[] = array( '__tag' => 'div', wfMsg( 'qp_results_interpretation_header' ) ); } # currently, error is not stored in DB, only the vote and long / short interpretations # todo: is it worth to store it? if ( ( $scriptError = $ctrl->error ) != '' ) { $interp[] = array( '__tag' => 'div', 'class' => 'interp_error', qp_Setup::specialchars( $scriptError ) ); } # output long result, when permitted and available if ( $this->showInterpretation['long'] && ( $answer = $ctrl->long ) !== '' ) { if ( $showDescriptions ) { $interp[] = array( '__tag' => 'div', 'class' => 'interp_header', wfMsg( 'qp_results_long_interpretation' ) ); } $interp[] = array( '__tag' => 'div', 'class' => 'interp_answer_body', is_null( $this->pview ) ? nl2br( qp_Setup::specialchars( $answer ) ) : $this->pview->rtp( $answer ) ); } # output short result, when permitted and available if ( $this->showInterpretation['short'] && ( $answer = $ctrl->short ) !== '' ) { if ( $showDescriptions ) { $interp[] = array( '__tag' => 'div', 'class' => 'interp_header', wfMsg( 'qp_results_short_interpretation' ) ); } $interp[] = array( '__tag' => 'div', 'class' => 'interp_answer_body', nl2br( qp_Setup::specialchars( $answer ) ) ); } if ( $this->showInterpretation['structured'] && ( $answer = $ctrl->structured ) !== '' ) { if ( $showDescriptions ) { $interp[] = array( '__tag' => 'div', 'class' => 'interp_header', wfMsg( 'qp_results_structured_interpretation' ) ); } $strucTable = $ctrl->getStructuredAnswerTable(); $rows = array(); foreach ( $strucTable as &$line ) { if ( isset( $line['keys'] ) ) { # current node is associative array qp_Renderer::addRow( $rows, $line['keys'], array(), 'th' ); qp_Renderer::addRow( $rows, $line['vals'] ); } else { # current node is scalar value qp_Renderer::addRow( $rows, array( $line['vals'] ) ); } } $interp[] = array( '__tag' => 'table', 'class' => 'structured_answer', $rows ); unset( $strucTable ); } $tagarray[] = array( '__tag' => 'div', 'class' => 'interp_answer', $interp ); }
/** * Displays current poll actions links. */ function showPollActionsList( $pid, $poll_id, Title $poll_title ) { global $wgContLang; return wfMsg( 'qp_results_line_qpl', # pagename qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) ), # polltitle qp_Setup::specialchars( $poll_id ), # goto link $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) ), # voices link $this->qpLink( $this->getTitle(), wfMsg( 'qp_stats_link' ), array(), array( "id" => intval( $pid ), "action" => "stats" ) ), # users link $this->qpLink( $this->getTitle(), wfMsg( 'qp_users_link' ), array(), array( "id" => intval( $pid ), "action" => "pulist" ) ), # not participated link $this->qpLink( $this->getTitle(), wfMsg( 'qp_not_participated_link' ), array(), array( "id" => intval( $pid ), "action" => "npulist" ) ) ); }
/** * @return string html representation of user vote for Special:Pollresults output */ function displayUserVote() { $ctrl = $this->ctrl; $output = $this->displayHeader(); $output .= "<div class=\"qpoll\">\n" . "<table class=\"qdata\">\n"; foreach ( $ctrl->ProposalText as $propkey => &$serialized_tokens ) { if ( !is_array( $dbtokens = unserialize( $serialized_tokens ) ) ) { throw new MWException( 'dbtokens is not an array in ' . __METHOD__ ); } $catId = 0; $row = array(); foreach ( $dbtokens as &$token ) { if ( is_string( $token ) ) { # add a proposal part $row[] = array( '__tag' => 'span', 'class' => 'prop_part', qp_Setup::entities( $token ) ); } elseif ( is_array( $token ) ) { # add a category definition with selected text answer (if any) # resulting category view tagarray $catview = array( '__tag' =>'span', 'class' => 'cat_part', '' // text_answer ); if ( array_key_exists( $propkey, $ctrl->ProposalCategoryId ) && ( $id_key = array_search( $catId, $ctrl->ProposalCategoryId[$propkey] ) ) !== false ) { if ( ( $text_answer = $ctrl->ProposalCategoryText[$propkey][$id_key] ) === '' ) { if ( count( $token ) === 1 ) { # indicate selected checkbox / radiobuttn $catview[0] = qp_Setup::RESULTS_CHECK_SIGN; } } else { # text answer is not empty; # try to extract select multiple, if any $text_answer = explode( qp_Setup::SELECT_MULTIPLE_VALUES_SEPARATOR, $text_answer ); # place unused categories into the value of 'title' attribute $titleAttr = ''; foreach ( $token as &$option ) { if ( !in_array( $option, $text_answer ) ) { if ( $titleAttr !== '' ) { $titleAttr .= ' | '; } $titleAttr .= qp_Setup::entities( $option ); } } if ( count( $text_answer ) > 1 ) { # selected multiple values; # re-create the view for multiple category parts $catview = array(); foreach ( $text_answer as $key => &$cat_part ) { $tag = array( '__tag' => 'span', 'class' => 'cat_part', 'title' => $titleAttr, qp_Setup::specialchars( $cat_part ) ); if ( in_array( $cat_part, $token ) ) { $tag['class'] .= ( $key % 2 === 0 ) ? ' cat_even' : ' cat_odd'; } else { # add 'cat_unanswered' CSS class only to select multiple values $tag['class'] .= ' cat_unanswered'; } if ( $key == 0 ) { $tag['class'] .= ' cat_first'; } $catview[] = $tag; } } else { # text input or textarea $catview['title'] = $titleAttr; # note that count( $text_answer) here cannot be zero, because # explode() was performed on non-empty $text_answer $catview[0] = qp_Setup::specialchars( array_pop( $text_answer ) ); } } } else { # many browsers trim the spaces between spans when the text node is empty; # use non-breaking space to prevent this $catview[0] = ' '; $catview['class'] .= ' cat_unanswered'; } $row[] = $catview; # move to the next category (if any) $catId++; } else { throw new MWException( 'DB token has invalid type (' . gettype( $token ) . ') in ' . __METHOD__ ); } } $output .= qp_Renderer::displayRow( array( $row ), array( 'class' => 'qdatatext' ) ); } $output .= "</table>\n" . "</div>\n"; return $output; }
/** * Get proposal attributes from raw proposal text (source page text or DB field) * * @param $proposal_text string raw proposal text */ public function getFromSource( $proposal_text ) { # set default values of properties $this->error = 0; $this->name = ''; $this->dbText = $this->catreq = null; $this->emptytext = null; $this->cpdef = $proposal_text; $matches = array(); # try to match the raw proposal name (without specific attributes) preg_match( '/^:\|\s*(.+?)\s*\|\s*(.+?)\s*$/su', $this->cpdef, $matches ); if ( count( $matches ) < 3 || ( $this->name = $matches[1] ) === '' ) { # raw proposal name is not defined or empty return; } # check, whether raw proposal name will fit into the corresponding DB field if ( strlen( $this->getAttrDef() ) >= qp_Setup::$field_max_len['proposal_text'] ) { $this->setError( 'qp_error_too_long_proposal_name' ); return; } # try to get xml-like attributes; $paramkeys = qp_Setup::getXmlLikeAttributes( $this->name, array( 'name', 'catreq', 'emptytext' ) ); if ( $paramkeys['name'] !== null ) { # name attribute found $this->name = trim( $paramkeys['name'] ); } if ( $paramkeys['catreq'] !== null ) { $this->catreq = self::getSaneCatReq( $paramkeys['catreq'] ); } if ( $paramkeys['emptytext'] !== null ) { $this->emptytext = self::getSaneEmptyText( $paramkeys['emptytext'] ); } if ( is_numeric( $this->name ) ) { $this->setError( 'qp_error_numeric_proposal_name' ); return; } elseif ( preg_match( '/$.^/msu', $this->name ) ) { $this->setError( 'qp_error_multiline_proposal_name' ); return; } # remove raw proposal name from proposal definition $this->cpdef = $matches[2]; }
/** * Add string part to value of current last option * @param $token string current value of token between pipe separators * Also, _optionally_ parses xml-like attributes (when these are found in category definition) */ function addToLastOption( $token ) { $matches = array(); 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', 'height', 'sorting', 'multiple' ) ); # 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; }
function formatResult( $result ) { global $wgLang, $wgContLang; $link = ""; if ( $result !== null ) { $uid = intval( $result->uid ); $userName = $result->username; $userTitle = Title::makeTitleSafe( NS_USER, $userName ); $user_link = $this->qpLink( $userTitle, $userName ); $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ( $this->inverse ? "_inv" : "" ) ), array(), array( "id" => intval( $this->pid ), "uid" => $uid, "action" => "uvote" ) ); $text_answer = ( $result->text_answer == '' ) ? '' : '<i>' . qp_Setup::entities( $result->text_answer ) . '</i>'; $link = wfMsg( 'qp_results_line_qucl', $user_link, $voice_link, $text_answer ); } return $link; }
/** * Creates question view which should be renreded and * also may be altered during the poll generation * todo: this method is too long, split to smaller parts */ function parseBody() { global $wgContLang; $proposalPattern = '/^'; foreach ( $this->mCategories as $catDesc ) { $proposalPattern .= '(\[\]|\(\)|<>)'; } $proposalPattern .= '(.*)/su'; $proposalId = -1; # set static view state for the future qp_TabularQuestionProposalView instances qp_TabularQuestionProposalView::applyViewState( $this->view ); $prop_attrs = qp_Setup::$propAttrs; $prop_attrs->setQuestion( $this ); while ( $prop_attrs->iterate() ) { # new proposal view $pview = new qp_TabularQuestionProposalView( $proposalId + 1, $this ); # get the list of categories ($matches) if ( preg_match( $proposalPattern, $prop_attrs->cpdef, $matches ) ) { $prop_attrs->dbText = array_pop( $matches ); // current proposal text array_shift( $matches ); // remove "at whole" match $last_matches = $matches; } else { if ( $proposalId >= 0 ) { # shortened syntax: use the pattern from the last row where it's been defined $prop_attrs->dbText = $prop_attrs->cpdef; $matches = $last_matches; } } if ( $prop_attrs->dbText === null ) { continue; } $pview->text = $prop_attrs->dbText; $proposalId++; # set proposal name (if any) if ( is_string( $prop_attrs->error ) ) { $pview->prependErrorMessage( wfMsg( $prop_attrs->error, 'error' ) ); } elseif ( $prop_attrs->name !== '' ) { $this->mProposalNames[$proposalId] = $prop_attrs->name; } $this->mProposalText[$proposalId] = strval( $prop_attrs ); # Determine a type ID, according to the questionType and the number of signes. foreach ( $this->mCategories as $catId => $catDesc ) { $typeId = $matches[$catId]; # start new input field tag (category) $pview->addNewCategory( $catId ); $inp = array( '__tag' => 'input' ); # Determine the input's name and value. $name = "q{$this->mQuestionId}p{$proposalId}s{$catId}"; switch ( $typeId ) { case '<>': $value = ''; $inputType = 'text'; break; case '[]': $value = "s{$catId}"; $inputType = 'checkbox'; break; case '()': $value = "s{$catId}"; $inputType = 'radio'; break; } # Determine if the input has to be checked. $input_checked = false; $text_answer = ''; if ( $this->poll->mBeingCorrected && qp_Setup::$request->getVal( $name ) !== null ) { if ( $inputType == 'text' ) { $text_answer = trim( qp_Setup::$request->getText( $name ) ); if ( strlen( $text_answer ) > qp_Setup::$field_max_len['text_answer'] ) { $text_answer = $wgContLang->truncate( $text_answer, qp_Setup::$field_max_len['text_answer'] , '' ); } if ( $prop_attrs->emptytext || $text_answer != '' ) { $input_checked = true; } } else { $inp['checked'] = 'checked'; $input_checked = true; } } if ( ( $prev_text_answer = $this->answerExists( $inputType, $proposalId, $catId ) ) !== false ) { $input_checked = true; if ( $inputType == 'text' ) { $text_answer = $prev_text_answer; } else { $inp['checked'] = 'checked'; } } if ( $input_checked === true ) { # add category to the list of user answers for current proposal (row) $this->mProposalCategoryId[ $proposalId ][] = $catId; $this->mProposalCategoryText[ $proposalId ][] = $text_answer; } # always borderless (mixed questions do not have spans) $pview->setCategorySpan(); # unique (poll,question,proposal,category) "coordinate" for javascript $inp['id'] = "mx{$this->poll->mOrderId}q{$this->mQuestionId}p{$proposalId}c{$catId}"; $inp['class'] = 'check'; $inp['type'] = $inputType; $inp['name'] = $name; if ( $inputType == 'text' ) { $inp['value'] = qp_Setup::specialchars( $text_answer ); if ( $this->view->textInputStyle != '' ) { $inp['style'] = $this->view->textInputStyle; } } else { $inp['value'] = $value; } $pview->setCat( $inp ); } try { # if there is only one category defined and it is not a textfield, # the question has a syntax error if ( count( $matches ) < 2 && $matches[0] != '<>' ) { $pview->setErrorMessage( wfMsg( 'qp_error_too_few_categories' ), 'error' ); throw new Exception( 'qp_error' ); } # If the proposal text is empty, the question has a syntax error. if ( trim( $pview->text ) == '' ) { $pview->setErrorMessage( wfMsg( 'qp_error_proposal_text_empty' ), 'error' ); throw new Exception( 'qp_error' ); } if ( $this->poll->mBeingCorrected && $prop_attrs->hasMissingCategories( $answered_cats_count = $this->getAnsweredCatCount( $proposalId ), count( $this->mCategories ) ) ) { # the proposal was submitted but has not enough categories answered $pview->prependErrorMessage( ($answered_cats_count > 0) ? wfMsg( 'qp_error_not_enough_categories_answered' ) : wfMsg( 'qp_error_no_answer' ) , 'NA' ); # if there was no previous errors, hightlight the whole row if ( $this->getState() == '' ) { throw new Exception( 'qp_error' ); } } } catch ( Exception $e ) { if ( $e->getMessage() == 'qp_error' ) { $pview->addCellsClass( 'error' ); } else { throw new MWException( $e->getMessage() ); } } $this->view->addProposal( $proposalId, $pview ); } }
/** * todo: unfortunately, rendering of the question also conditionally modifies * state of poll controller * @modifies parent controller * @return string html representation of the question */ function renderQuestion() { $output_table = array( '__tag' => 'table', '__end' => "\n", 'class' => 'object' ); if ( $this->propWidth !== '' ) { $output_table['style'] = 'width:100%;'; } # Determine the side border color the question. if ( $this->ctrl->getState() != '' ) { if ( isset( $output_table['class'] ) ) { $output_table['class'] .= ' error_mark'; } else { $output_table['class'] = 'error_mark'; } # set poll controller state according to question controller state $this->ctrl->applyStateToParent(); } $output_table[] = array( '__tag' => 'tbody', '__end' => "\n", 0 => $this->renderTable() ); $tags = array(); if ( $this->ctrl->poll->questions->usedCount() > 1 ) { # display question number only if there are more than one question in poll $tags[] = array( '__tag' => 'div', '__end' => "\n", 'class' => 'header', array( '__tag' => 'span', 'class' => 'questionId', 0 => $this->ctrl->usedId ) ); } if ( $this->headerErrorMessage !== '' ) { # either fatal or proposal error occured $tags[] = array( '__tag' => 'div', 'class' => ( $this->ctrl->getState() === 'error' ) ? 'fatalerror' : 'proposalerror', qp_Setup::specialchars( $this->headerErrorMessage ) ); } $tags[] = array( '__tag' => 'div', $this->rtp( $this->ctrl->mCommonQuestion ) ); # class 'question_mod4_[0-3]' is used to prettify question table cells; # todo: at some later point, when HTML5/CSS3 will take over, this will not be needed. $tags = array( '__tag' => 'div', '__end' => "\n", 'class' => 'question question_mod4_' . ( $this->ctrl->usedId % 4 ), $tags ); $tags[] = &$output_table; return qp_Renderer::renderTagArray( $tags ); }
/** * Add category as select / option list tagarray */ function addSelect( stdClass $elem, $className ) { if ( $elem->options[0] !== '' ) { # default element in select/option set always must be an empty option array_unshift( $elem->options, '' ); } $html_options = array(); # prepare the list of selected values if ( $elem->attributes['multiple'] !== null ) { # new lines are separator for selected multiple options $selected_values = explode( qp_Setup::SELECT_MULTIPLE_VALUES_SEPARATOR, $elem->value ); } else { $selected_values = array( $elem->value ); } # generate options list foreach ( $elem->options as $option ) { $html_option = array( '__tag' => 'option', 'value' => qp_Setup::entities( $option ), qp_Setup::specialchars( $option ) ); if ( in_array( $option, $selected_values ) ) { $html_option['selected'] = 'selected'; } $html_options[] = $html_option; } $select = array( '__tag' => 'select', # unique (poll_type,order_id,question,proposal,category) "coordinate" for javascript 'id' => "{$this->id_prefix}c{$this->catId}", 'class' => $className, 'name' => $elem->name, $html_options ); # multiple options 'name' attribute should have array hint [] if ( $elem->attributes['multiple'] !== null ) { $select['multiple'] = 'multiple'; $select['name'] .= '[]'; } # determine visual height of select options list if ( ( $size = $elem->attributes['height'] ) !== 0 ) { if ( is_int( $size ) ) { if ( count( $elem->options ) < $size ) { $size = count( $elem->options ); } } else { /* 'auto' */ $size = count( $elem->options ); } $select['size'] = $size; } $this->cell[] = $select; $this->catId++; }
/** * Checks the submitted eval code for errors * In case of success returns transformed code, which is safer for eval * @param $sourceCode string * submitted code which has to be eval'ed (no php tags) * @param $destinationCode string * transformed code (in case of success) (no php tags) * @return mixed * boolean true in case of success; * string error message on failure; */ static function checkAndTransformCode( $sourceCode, &$destinationCode ) { # tokenizer requires php tags to parse propely, # eval(), however requires not to have php tags - weird.. $tokens = token_get_all( "<?php $sourceCode ?>" ); /* remove <?php ?> */ array_shift( $tokens ); array_pop( $tokens ); $destinationCode = ''; $prev_token = null; foreach ( $tokens as $token ) { if ( is_array( $token ) ) { list( $token_id, $content, $line ) = $token; # check against generic list of disallowed tokens if ( !in_array( $token_id, self::$allowedTokens, true ) ) { return wfMsg( 'qp_error_eval_illegal_token', token_name( $token_id ), qp_Setup::specialchars( $content ), $line ); } if ( $token_id == T_VARIABLE ) { $prev_content = is_array( $prev_token ) ? $prev_token[1] : $prev_token; preg_match( '`(\$)$`', $prev_content, $matches ); # disallow variable variables if ( count( $matches ) > 1 && $matches[1] == '$' ) { return wfMsg( 'qp_error_eval_variable_variable_access', token_name( $token_id ), qp_Setup::specialchars( $content ), $line ); } # disallow superglobals if ( in_array( $content, self::$superGlobals ) ) { return wfMsg( 'qp_error_eval_illegal_superglobal', token_name( $token_id ), qp_Setup::specialchars( $content ), $line ); } # restrict variable names preg_match( '`^(\$)([A-Za-z0-9_]*)$`', $content, $matches ); if ( count( $matches ) != 3 ) { return wfMsg( 'qp_error_eval_illegal_variable_name', token_name( $token_id ), qp_Setup::specialchars( $content ), $line ); } # correct variable names into pseudonamespace 'qpv_' $content = "\$" . self::$pseudoNamespace . $matches[2]; } # do not count whitespace as previous token if ( $token_id != T_WHITESPACE ) { $prev_token = $token; } # concat corrected token to the destination $destinationCode .= $content; } else { if ( $token == '(' && is_array( $prev_token ) ) { list( $token_id, $content, $line ) = $prev_token; # disallow variable function calls if ( $token_id === T_VARIABLE ) { return wfMsg( 'qp_error_eval_variable_function_call', token_name( $token_id ), qp_Setup::specialchars( $content ), $line ); } # disallow non-allowed function calls based on the list if ( $token_id === T_STRING && array_search( $content, self::$allowedCalls, true ) === false ) { return wfMsg( 'qp_error_eval_illegal_function_call', token_name( $token_id ), qp_Setup::specialchars( $content ), $line ); } } $prev_token = $token; # concat current token to the destination $destinationCode .= $token; } } return true; }
/** * qp_*QuestionData instantiator (factory). * Please use it instead of qp_*QuestionData constructors when * creating qdata instances. */ static function factory( $argv ) { $type = is_array( $argv ) ? $argv['type'] : $argv->mType; switch ( $type ) { case 'textQuestion' : return new qp_TextQuestionData( $argv ); case 'singleChoice' : case 'multipleChoice' : case 'mixedChoice' : return new qp_QuestionData( $argv ); default : throw new MWException( 'Unknown type of question ' . qp_Setup::specialchars( $type ) . ' in ' . __METHOD__ ); } }
function getQuestionData($qid) { $this->question_id = $qid; $this->error_message = 'invalid_question_id'; if (preg_match(qp_Setup::PREG_POSITIVE_INT4_MATCH, $this->question_id)) { $this->question_id = intval($this->question_id); $this->pollStore->loadQuestions(); $this->pollStore->setLastUser(qp_Setup::getCurrUserName()); $this->pollStore->loadUserVote(); $this->error_message = 'missing_question_id'; if (array_key_exists($this->question_id, $this->pollStore->Questions)) { return $this->pollStore->Questions[$this->question_id]; } } return false; }
function parseBody() { if ( $this->mType === 'singleChoice' ) { $this->questionParseBody( 'radio' ); } elseif ( $this->mType === 'multipleChoice' ) { $this->questionParseBody( 'checkbox' ); } else { throw new MWException( 'Cannot parse question with type=' . qp_Setup::specialchars( $this->mType ) ); } }
/** * Builds the question with fully parsed headers * * internally, the header is split into * main header (part inside curly braces) and * body header (categories and metacategories defitions) * * @param $header : the text of question "main" header (common question and XML-like attrs) * @param $body : the text of question body * for tabular questions body begins with header line that defines * categories and spans, followed by proposal lines) * @return question object with parsed headers */ function parseQuestionHeader( $header, $body ) { # parse questions common question and XML attributes $question = $this->parseMainHeader( $header ); if ( $question->getState() != 'error' ) { # load previous user choice, when it's available and DB header is compatible with parsed header if ( !method_exists( $question, 'parseBody' ) ) { $question->setState( 'error', wfMsgHtml( 'qp_error_question_not_implemented', qp_Setup::entities( $question->mType ) ) ); } elseif ( $body === '' ) { $question->setState( 'error', wfMsgHtml( 'qp_error_question_empty_body' ) ); } else { # build $question->raws[] $question->splitRawProposals( $body ); # parse the categories and spans (metacategories) $question->parseBodyHeader( $body ); } } return $question; }
/** * Show interpetation script source with line numbering (for debugging convenience) * * @param $input Text between <qpinterpret> and </qpinterper> tags, subset of PHP syntax. * @param $argv An array containing any arguments passed to the extension * @param &$parser The wikitext parser. * @param &$frame PPFrame object passed in MW 1.16+ * @return script source with line numbering */ static function showScript($input, $argv, $parser, $frame = false) { $lines_count = count(preg_split('`(\\r\\n|\\n|\\r)`', $input, -1)); $line_numbers = ''; if (!isset($argv['lang'])) { return '<strong class="error">' . wfMsg('qp_error_eval_missed_lang_attr') . '</strong>'; } $lang = $argv['lang']; if (!array_key_exists($lang, self::$scriptLinesCount)) { self::$scriptLinesCount[$lang] = 1; } $slc =& self::$scriptLinesCount[$lang]; for ($i = $slc; $i < $slc + $lines_count; $i++) { $line_numbers .= "{$i}\n"; } $slc = $i; $out = array('__tag' => 'div', 'class' => 'qpoll', 0 => array()); if (is_string($lintResult = qp_Interpret::lint($lang, $input))) { $out[0][] = array('__tag' => 'div', 'class' => 'interp_error', qp_Setup::specialchars($lintResult)); } $out[0][] = array('__tag' => 'div', 'class' => 'line_numbers', $line_numbers); $out[0][] = array('__tag' => 'div', 'class' => 'script_view', qp_Setup::specialchars($input) . "\n"); $markercount = count(self::$markerList); $marker = "!qpoll-script-view{$markercount}-qpoll!"; self::$markerList[$markercount] = qp_Renderer::renderTagArray($out); return $marker; }
/** * Parses attribute line of the question * @param $attr_str attribute string from questions header * @modifies $paramkeys array key is attribute regexp, value is the value of attribute * @return string the value of question's type attribute */ function getQuestionAttributes( $attr_str, array &$paramkeys ) { $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 ) { $paramkeys[$attr] = $val; } } return isset( $paramkeys[ 't[yi]p[eo]' ] ) ? trim( $paramkeys[ 't[yi]p[eo]' ] ) : ''; }
/** * @return string html representation of question statistics */ public function displayStats( qp_SpecialPage $page, $pid ) { $ctrl = $this->ctrl; $current_title = $page->getTitle(); $output = $this->displayHeader() . "<div class=\"qpoll\">\n" . "<table class=\"qdata\">\n" . qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $ctrl->CategorySpans ), array( 'class' => 'spans' ), 'th', array( 'count' => 'colspan', 'name' => 0 ) ) . qp_Renderer::displayRow( array_map( array( $this, 'categoryentities' ), $ctrl->Categories ), array(), 'th', array( 'name' => 0 ) ); # multiple choice polls doesn't use real spans, instead, every column is like "span" $spansUsed = count( $ctrl->CategorySpans ) > 0 || $ctrl->type == "multipleChoice"; foreach ( $ctrl->ProposalText as $propkey => &$proposal_text ) { if ( isset( $ctrl->Votes[ $propkey ] ) ) { if ( $ctrl->Percents === null ) { $row = $ctrl->Votes[ $propkey ]; } else { $row = $ctrl->Percents[ $propkey ]; foreach ( $row as $catkey => &$cell ) { # Replace spaces with en spaces $formatted_cell = str_replace( " ", " ", sprintf( '%3d%%', intval( round( 100 * $cell ) ) ) ); # only percents !=0 are displayed as link if ( $cell == 0.0 && $ctrl->question_id !== null ) { $cell = array( 0 => $formatted_cell, "style" => "color:gray" ); } else { $cell = array( 0 => $page->qpLink( $current_title, $formatted_cell, array( "title" => wfMsgExt( 'qp_votes_count', array( 'parsemag' ), $ctrl->Votes[ $propkey ][ $catkey ] ) ), array( "action" => "qpcusers", "id" => $pid, "qid" => $ctrl->question_id, "pid" => $propkey, "cid" => $catkey ) ) ); } if ( $spansUsed ) { if ( $ctrl->type == "multipleChoice" ) { $cell[ "class" ] = ( ( $catkey & 1 ) === 0 ) ? "spaneven" : "spanodd"; } else { $cell[ "class" ] = ( ( $ctrl->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? "spaneven" : "spanodd"; } } else { $cell[ "class" ] = "stats"; } } } } else { # this proposal has no statistics (no votes) $row = array_fill( 0, count( $ctrl->Categories ), '' ); } $row[] = array( 0 => qp_Setup::entities( $proposal_text ), "style" => "text-align:left;" ); $output .= qp_Renderer::displayRow( $row ); } $output .= "</table>\n" . "</div>\n"; return $output; }
/** * Encloses the output of $this->renderQuestionViews() into the output tag wrappers * @return rendered "final" html */ function renderPoll() { $pollStore = $this->ctrl->pollStore; # Generates the output. $qpoll_div = array( '__tag' => 'div', 'class' => 'qpoll' ); $qpoll_div[] = array( '__tag' => 'a', 'name' => $this->ctrl->getPollTitleFragment( null, '' ), 0 => '' ); # output script-generated error, when available # render short/long/structured result, when permitted and available $interpResultView = qp_InterpResultView::newFromBaseView( $this ); $interpResultView->showInterpResults( $qpoll_div, $pollStore->interpResult ); # unused anymore unset( $interpResultView ); # create voting form and fill it with messages and inputs $qpoll_form = array( '__tag' => 'form', 'method' => 'post', 'action' => $this->ctrl->getPollTitleFragment(), 'autocomplete' => 'off', '__end' => "\n" ); $qpoll_div[] = &$qpoll_form; # Determine the content of the settings table. $settings = Array(); if ( $this->ctrl->mState != '' ) { $settings[0][] = array( '__tag' => 'td', 'class' => 'margin object_error' ); $settings[0][] = array( '__tag' => 'td', 0 => wfMsgHtml( 'qp_result_' . $this->ctrl->mState ) ); } # Build the settings table. if ( count( $settings ) > 0 ) { $settingsTable = array( '__tag' => 'table', 'class' => 'settings', '__end' => "\n" ); foreach ( $settings as $settingsTr ) { $settingsTable[] = array( '__tag' => 'tr', 0 => $settingsTr, '__end' => "\n" ); } $qpoll_form[] = &$settingsTable; } $qpoll_form[] = array( '__tag' => 'input', 'type' => 'hidden', 'name' => 'pollId', 'value' => $this->ctrl->mPollId ); $qpoll_form[] = array( '__tag' => 'div', 'class' => 'pollQuestions', 0 => $this->renderQuestionViews() ); $submitBtn = array( '__tag' => 'input', 'type' => 'submit' ); $submitMsg = 'qp_vote_button'; if ( $pollStore->isAlreadyVoted() ) { $submitMsg = 'qp_vote_again_button'; } if ( $this->ctrl->mBeingCorrected ) { if ( $pollStore->getState() == "complete" ) { $submitMsg = 'qp_vote_again_button'; } } else { if ( $pollStore->getState() == "error" ) { $submitBtn['disabled'] = 'disabled'; } } $atLeft = $this->ctrl->attemptsLeft(); if ( $atLeft === false ) { $submitBtn['disabled'] = 'disabled'; } # disable submit button in preview mode & printable version if ( qp_Setup::$request->getVal( 'action' ) == 'parse' || qp_Setup::$output->isPrintable() ) { $submitBtn['disabled'] = 'disabled'; } $submitBtn['value'] = wfMsgHtml( $submitMsg ); $p = array( '__tag' => 'p' ); $p[] = $submitBtn; # output "no more attempts" message, when applicable if ( $atLeft === false ) { $p[] = array( '__tag' => 'span', 'class' => 'attempts_counter', qp_Setup::specialchars( wfMsg( 'qp_error_no_more_attempts' ) ) ); } elseif ( $atLeft !== true ) { $p[] = array( '__tag' => 'span', 'class' => 'attempts_counter', qp_Setup::specialchars( wfMsgExt( 'qp_submit_attempts_left', array( 'parsemag' ), intval( $atLeft ) ) ) ); } $qpoll_form[] = &$p; return qp_Renderer::renderTagArray( $qpoll_div ); }