/**
	 * 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 );
	}
	/**
	 * Interpretates the answer with selected script
	 * @param $interpretScript
	 *   string  source code of interpretation script
	 * @param $injectVars
	 *   array of PHP data to inject into interpretation script;
	 *     key of element will become variable name in the interpretation script;
	 *     value of element will become variable value in the interpretation script;
	 * @param $interpResult  qp_InterpResult
	 * @modifies $interpResult
	 * @return  mixed
	 *   array script result to check
	 *   qp_InterpResult  $interpResult (in case of error)
	 */
	static function interpretAnswer(
			$interpretScript,
			array $injectVars,
			qp_InterpResult $interpResult ) {
		# template page evaluation
		if ( ( $check = self::selfCheck() ) !== true ) {
			# self-check error
			return $interpResult->setError( wfMsg( 'qp_error_eval_self_check', $check ) );
		}
		$evalScript = '';
		if ( ( $check = self::checkAndTransformCode( $interpretScript, $evalScript ) ) !== true ) {
			# possible malicious code
			return $interpResult->setError( $check );
		}
		# inject poll answer into the interpretation script
		$evalInject = '';
		foreach ( $injectVars as $varname => $var ) {
			$evalInject .= "\$" . self::$pseudoNamespace . "{$varname} = unserialize( base64_decode( '" . base64_encode( serialize( $var ) ) . "' ) ); ";
		}
		$evalScript = "{$evalInject}/* */ {$evalScript}";
		$result = eval( $evalScript );
		return $result;
	}
	/**
	 * Glues the content of <qpinterpret> tags together, checks "lang" attribute
	 * and calls appropriate interpretator to evaluate the user answer
	 *
	 * @param $interpArticle  _existing_ Article with interpretation script enclosed in <qpinterp> tags
	 * @param $injectVars  array with the following possible keys:
	 *                     key 'answer' array of user selected categories for
	 *                     every proposal & question of the poll;
	 *                     key 'usedQuestions' array of used questions for randomized polls
	 *                     or false, when the poll questions were not randomized
	 * @return instance of qp_InterpResult class (interpretation result)
	 */
	static function getResult( Article $interpArticle, array $injectVars ) {
		global $wgParser, $wgContLang;
		$matches = array();
		# extract <qpinterpret> tags from the article content
		$wgParser->extractTagsAndParams( array( qp_Setup::$interpTag ), $interpArticle->getRawText(), $matches );
		$interpResult = new qp_InterpResult();
		# glue content of all <qpinterpret> tags at the page together
		$interpretScript = '';
		$lang = '';
		foreach ( $matches as &$match ) {
			list( $tagName, $content, $attrs ) = $match;
			# basic checks for lang attribute (only lang="php" is implemented yet)
			# however we do not want to limit interpretation language,
			# so the attribute is enforced to use
			if ( !isset( $attrs['lang'] ) ) {
				return $interpResult->setError( wfMsg( 'qp_error_eval_missed_lang_attr' ) );
			}
			if ( $lang == '' ) {
				$lang = $attrs['lang'];
			} elseif ( $attrs['lang'] != $lang ) {
				return $interpResult->setError( wfMsg( 'qp_error_eval_mix_languages', $lang, $attrs['lang'] ) );
			}
			if ( $tagName == qp_Setup::$interpTag ) {
				$interpretScript .= $content;
			}
		}
		switch ( $lang ) {
		case 'php' :
			$result = qp_Eval::interpretAnswer( $interpretScript, $injectVars, $interpResult );
			if ( $result instanceof qp_InterpResult ) {
				# evaluation error (environment error) , return it;
				return $interpResult;
			}
			break;
		default :
			return $interpResult->setError( wfMsg( 'qp_error_eval_unsupported_language', $lang ) );
		}
		/*** process the result ***/
		if ( !is_array( $result ) ) {
			return $interpResult->setError( wfMsg( 'qp_error_interpretation_no_return' ) );
		}
		if ( isset( $result['options'] ) &&
				is_array( $result['options'] ) &&
				array_key_exists( 'store_erroneous', $result['options'] ) ) {
			$interpResult->storeErroneous = (boolean) $result['options']['store_erroneous'];
		}
		if ( isset( $result['error'] ) && is_array( $result['error'] ) ) {
			# initialize $interpResult->qpcErrors[] member array
			foreach ( $result['error'] as $qidx => $question ) {
				if ( is_array( $question ) ) {
					foreach ( $question as $pidx => $prop_error ) {
						# integer indicates proposal id; string - proposal name
						if ( is_array( $prop_error ) ) {
							# separate error messages list for proposal categories
							foreach ( $prop_error as $cidx => $cat_error ) {
								$interpResult->setQPCerror( $cat_error, $qidx, $pidx, $cidx );
							}
						} else {
							# error message for the whole proposal line
							$interpResult->setQPCerror( $prop_error, $qidx, $pidx );
						}
					}
				}
			}
		}
		if ( isset( $result['errmsg'] ) && trim( strval( $result['errmsg'] ) ) != '' ) {
			# script-generated error message for the whole answer
			return $interpResult->setError( (string) $result['errmsg'] );
		}
		# if there were question/proposal errors, return them;
		if ( $interpResult->isError() ) {
			return $interpResult->setDefaultErrorMessage();
		}
		$interpCount = 0;
		foreach ( qp_Setup::$show_interpretation as $interpType => $show ) {
			if ( isset( $result[$interpType] ) ) {
				$interpCount++;
			}
		}
		if ( $interpCount == 0 ) {
			return $interpResult->setError( wfMsg( 'qp_error_interpretation_no_return' ) );
		}
		$interpResult->structured = isset( $result['structured'] ) ? serialize( $result['structured'] ) : '';
		if ( strlen( $interpResult->structured ) > qp_Setup::$field_max_len['serialized_interpretation'] ) {
			# serialized structured interpretation is too long and
			# this type of interpretation cannot be truncated
			unset( $interpResult->structured );
			return $interpResult->setError( wfMsg( 'qp_error_structured_interpretation_is_too_long' ) );
		}
		$interpResult->short = isset( $result['short'] ) ? strval( $result['short'] ) : '';
		$interpResult->long = isset( $result['long'] ) ? strval( $result['long'] ) : '';
		if ( strlen( $interpResult->long ) > qp_Setup::$field_max_len['long_interpretation'] ) {
			$interpResult->long = $wgContLang->truncate( $interpResult->long, qp_Setup::$field_max_len['long_interpretation'] , '' );
		}
		return $interpResult;
	}