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] = '&#160;';
						$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++;
	}
Esempio n. 11
0
	/**
	 * 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__ );
		}
	}
Esempio n. 13
0
 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 ) );
		}
	}
Esempio n. 15
0
	/**
	 * 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;
	}
Esempio n. 16
0
 /**
  * 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( " ", "&#8194;", 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;
	}
Esempio n. 19
0
	/**
	 * 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 );
	}