Index: trunk/extensions/QPoll/i18n/qp.i18n.php |
— | — | @@ -108,6 +108,7 @@ |
109 | 109 | 'qp_error_invalid_poll_id' => 'Invalid poll id (id=$1). |
110 | 110 | Poll id may contain only letters, numbers and space character.', |
111 | 111 | 'qp_error_already_used_poll_id' => 'The poll id has already been used on this page (id=$1).', |
| 112 | + 'qp_error_too_long_dependance_value' => 'The poll (id=$1) dependance attribute value (dependance="$2") is too long to be stored in database.', |
112 | 113 | 'qp_error_invalid_dependance_value' => 'The poll (id=$1) dependance chain has invalid value of dependance attribute (dependance="$2").', |
113 | 114 | 'qp_error_missed_dependance_title' => 'The poll (id=$1) is dependant on the another poll (id=$3) from page [[$2]], but the title [[$2]] was not found. |
114 | 115 | Either remove the dependance attribute, or restore [[$2]].', |
— | — | @@ -225,6 +226,9 @@ |
226 | 227 | 'qp_error_invalid_question_type' => '{{Identical|Invalid value of qustion xml-like "type" attribute was specified. There is no such type of question. Please read the manual for list of valid question types.}}', |
227 | 228 | 'qp_error_type_in_stats_mode' => 'Question\'s "type" xml-like attribute is meaningless in statistical display mode.', |
228 | 229 | 'qp_error_no_poll_id' => 'Every poll definition in declaration / voting mode must have "id" attribute.', |
| 230 | + 'qp_error_too_long_dependance_value' => 'Parameters: |
| 231 | +* $1 is the poll ID of the poll having an error. |
| 232 | +* $2 is the value of poll "dependance" attribute, which cannot be stored into database because it is too long.', |
229 | 233 | 'qp_error_missed_dependance_poll' => 'Parameters: |
230 | 234 | * $1 is the poll ID of the poll having an error. |
231 | 235 | * $2 is a link to the page with the poll, that this erroneous poll depends on. |
— | — | @@ -2707,6 +2711,7 @@ |
2708 | 2712 | 'qp_error_no_poll_id' => 'Тэг опроса не имеет атрибута id.', |
2709 | 2713 | 'qp_error_invalid_poll_id' => 'Недопустимый идентификатор опроса (id=$1). Идентификатор опроса может содержать только буквы, цифры и символ пробела', |
2710 | 2714 | 'qp_error_already_used_poll_id' => 'Установленный атрибут id опроса уже используется другим опросом на данной странице (id=$1).', |
| 2715 | + 'qp_error_too_long_dependance_value' => 'Значение атрибута зависимости опросов (dependance="$2") для опроса (id=$1) имеет слишком большую длину, из-за чего не может быть сохранено.', |
2711 | 2716 | 'qp_error_invalid_dependance_value' => 'В цепочке зависимости опросов для опроса (id=$1) было найдено синтаксически неверное значение атрибута зависимости (dependance="$2")', |
2712 | 2717 | 'qp_error_missed_dependance_title' => 'Опрос с идентификатором id=$1 имеет атрибут зависимости от другого опроса (id=$3), находящегося на отсутствующей странице [[$2]]. Необходимо убрать атрибут зависимости от другого опроса, либо восстановить страницу [[$2]]', |
2713 | 2718 | 'qp_error_missed_dependance_poll' => 'Опрос с идентификатором id=$1 требует прохождения другого опроса с идентификатором id=$3, находящегося на странице $2. Однако же, последний не был найден. Необходимо удалить атрибут зависимости из опроса (id=$1), либо создать опрос с идентификатором id=$3 на странице $2 и сохранить его. Для сохранения опроса будет достаточно нажать кнопку "Проголосовать", не отвечая ни на один вопрос.', |
Index: trunk/extensions/QPoll/clientside/qp_user.js |
— | — | @@ -188,6 +188,7 @@ |
189 | 189 | case 'tx' : |
190 | 190 | // handler for text question proposal rows |
191 | 191 | addEvent( input[j], "click", self.clickTextRow ); |
| 192 | + break; |
192 | 193 | } |
193 | 194 | } else { |
194 | 195 | // non-unique, non-mixed tabular questions |
Index: trunk/extensions/QPoll/model/qp_pollstore.php |
— | — | @@ -832,9 +832,10 @@ |
833 | 833 | } |
834 | 834 | |
835 | 835 | private function setQuestionDesc() { |
| 836 | + global $wgContLang; |
836 | 837 | $insert = array(); |
837 | 838 | foreach ( $this->Questions as $qkey => &$ques ) { |
838 | | - $insert[] = array( 'pid' => $this->pid, 'question_id' => $qkey, 'type' => $ques->type, 'common_question' => $ques->CommonQuestion ); |
| 839 | + $insert[] = array( 'pid' => $this->pid, 'question_id' => $qkey, 'type' => $ques->type, 'common_question' => $wgContLang->truncate( $ques->CommonQuestion, qp_Setup::$field_max_len['common_question'] , '' ) ); |
839 | 840 | $ques->question_id = $qkey; |
840 | 841 | } |
841 | 842 | if ( count( $insert ) > 0 ) { |
— | — | @@ -870,7 +871,7 @@ |
871 | 872 | if ( isset( $ques->ProposalNames[$propkey] ) ) { |
872 | 873 | $ptext = qp_QuestionData::getProposalNamePrefix( $ques->ProposalNames[$propkey] ) . $ptext; |
873 | 874 | } |
874 | | - $insert[] = array( 'pid' => $this->pid, 'question_id' => $qkey, 'proposal_id' => $propkey, 'proposal_text' => $wgContLang->truncate( $ptext, qp_Setup::$proposal_max_length , '' ) ); |
| 875 | + $insert[] = array( 'pid' => $this->pid, 'question_id' => $qkey, 'proposal_id' => $propkey, 'proposal_text' => $wgContLang->truncate( $ptext, qp_Setup::$field_max_len['proposal_text'] , '' ) ); |
875 | 876 | } |
876 | 877 | } |
877 | 878 | if ( count( $insert ) > 0 ) { |
Index: trunk/extensions/QPoll/ctrl/poll/qp_abstractpoll.php |
— | — | @@ -162,15 +162,29 @@ |
163 | 163 | } |
164 | 164 | } |
165 | 165 | |
166 | | - static function fatalError() { |
| 166 | + /** |
| 167 | + * Warning! When calling, do not forget to htmlquote the arguments, when required! |
| 168 | + */ |
| 169 | + static function fatalErrorNoQuote() { |
167 | 170 | $args = func_get_args(); |
168 | | - $result = 'Extension bug: ' . __METHOD__ . ' called without arguments'; |
169 | | - if ( count( $args > 0 ) ) { |
170 | | - $result = call_user_func_array( 'wfMsgHTML', $args ); |
| 171 | + if ( count( $args ) < 1 ) { |
| 172 | + throw new MWException( 'Too few arguments in ' . __METHOD__ ); |
171 | 173 | } |
172 | | - return '<div class="qpoll"><div class="fatalerror">' . $result . '</div></div>'; |
| 174 | + return '<div class="qpoll"><div class="fatalerror">' . call_user_func_array( 'wfMsgHTML', $args ) . '</div></div>'; |
173 | 175 | } |
174 | 176 | |
| 177 | + static function fatalErrorQuote() { |
| 178 | + $args = func_get_args(); |
| 179 | + if ( count( $args ) < 1 ) { |
| 180 | + throw new MWException( 'Too few arguments in ' . __METHOD__ ); |
| 181 | + } |
| 182 | + $key = array_shift( $args ); |
| 183 | + # quote the params (if any) |
| 184 | + $args = array_map( array( 'qp_Setup', 'specialchars' ), $args ); |
| 185 | + array_unshift( $args, $key ); |
| 186 | + return call_user_func_array( array( self, 'fatalErrorNoQuote' ), $args ); |
| 187 | + } |
| 188 | + |
175 | 189 | static function s_getPollTitleFragment( $pollid, $dash = '#' ) { |
176 | 190 | return $dash . 'qp_' . Title::escapeFragmentForURL( $pollid ); |
177 | 191 | } |
Index: trunk/extensions/QPoll/ctrl/poll/qp_pollstats.php |
— | — | @@ -55,11 +55,11 @@ |
56 | 56 | function setHeaders() { |
57 | 57 | if ( $this->mPollId !== null ) { |
58 | 58 | $this->mState = "error"; |
59 | | - return self::fatalError( 'qp_error_id_in_stats_mode' ); |
| 59 | + return self::fatalErrorNoQuote( 'qp_error_id_in_stats_mode' ); |
60 | 60 | } |
61 | 61 | if ( isset( $this->dependsOn ) && $this->dependsOn !== '' ) { |
62 | 62 | $this->mState = "error"; |
63 | | - return self::fatalError( 'qp_error_dependance_in_stats_mode' ); |
| 63 | + return self::fatalErrorNoQuote( 'qp_error_dependance_in_stats_mode' ); |
64 | 64 | } |
65 | 65 | return true; |
66 | 66 | } |
— | — | @@ -72,11 +72,11 @@ |
73 | 73 | function getPollStore() { |
74 | 74 | $this->pollStore = qp_PollStore::newFromAddr( $this->pollAddr ); |
75 | 75 | if ( !( $this->pollStore instanceof qp_PollStore ) || $this->pollStore->pid === null ) { |
76 | | - return self::fatalError( 'qp_error_no_such_poll', $this->pollAddr ); |
| 76 | + return self::fatalErrorQuote( 'qp_error_no_such_poll', $this->pollAddr ); |
77 | 77 | } |
78 | 78 | if ( !$this->pollStore->loadQuestions() ) { |
79 | 79 | $this->mState = "error"; |
80 | | - return self::fatalError( 'qp_error_no_stats', $this->pollAddr ); |
| 80 | + return self::fatalErrorQuote( 'qp_error_no_stats', $this->pollAddr ); |
81 | 81 | } |
82 | 82 | $this->pollStore->setLastUser( $this->username, false ); |
83 | 83 | # do not check the result, because we may show results even if the user hasn't voted |
Index: trunk/extensions/QPoll/ctrl/poll/qp_poll.php |
— | — | @@ -108,25 +108,28 @@ |
109 | 109 | function setHeaders() { |
110 | 110 | if ( $this->mPollId == null ) { |
111 | 111 | $this->mState = "error"; |
112 | | - return self::fatalError( 'qp_error_no_poll_id' ); |
| 112 | + return self::fatalErrorNoQuote( 'qp_error_no_poll_id' ); |
113 | 113 | } |
114 | 114 | if ( !self::isValidPollId( $this->mPollId ) ) { |
115 | 115 | $this->mState = "error"; |
116 | | - return self::fatalError( 'qp_error_invalid_poll_id', $this->mPollId ); |
| 116 | + return self::fatalErrorQuote( 'qp_error_invalid_poll_id', $this->mPollId ); |
117 | 117 | } |
118 | 118 | if ( !self::isUniquePollId( $this->mPollId ) ) { |
119 | 119 | $this->mState = "error"; |
120 | | - return self::fatalError( 'qp_error_already_used_poll_id', $this->mPollId ); |
| 120 | + return self::fatalErrorQuote( 'qp_error_already_used_poll_id', $this->mPollId ); |
121 | 121 | } |
122 | 122 | self::addPollId( $this->mPollId ); // add current poll id to the static list of poll ids on this page |
123 | 123 | if ( $this->pollAddr !== null ) { |
124 | 124 | $this->mState = "error"; |
125 | | - return self::fatalError( 'qp_error_address_in_decl_mode' ); |
| 125 | + return self::fatalErrorNoQuote( 'qp_error_address_in_decl_mode' ); |
126 | 126 | } |
127 | 127 | if ( $this->dependsOn != '' ) { |
| 128 | + if ( strlen( $this->dependsOn ) > qp_Setup::$field_max_len['dependance'] ) { |
| 129 | + return self::fatalErrorQuote( 'qp_error_too_long_dependance_value', $this->mPollId, $this->dependsOn ); |
| 130 | + } |
128 | 131 | $depsOnAddr = self::getPrefixedPollAddress( $this->dependsOn ); |
129 | 132 | if ( !is_array( $depsOnAddr ) ) { |
130 | | - return self::fatalError( 'qp_error_invalid_dependance_value', $this->mPollId, $this->dependsOn ); |
| 133 | + return self::fatalErrorQuote( 'qp_error_invalid_dependance_value', $this->mPollId, $this->dependsOn ); |
131 | 134 | } |
132 | 135 | $this->dependsOn = $depsOnAddr[2]; |
133 | 136 | } |
— | — | @@ -261,10 +264,10 @@ |
262 | 265 | $depPollId = $depPollStore->mPollId; |
263 | 266 | $depLink = $this->view->link( $depTitle, $depTitle->getPrefixedText() . ' (' . $depPollStore->mPollId . ')' ); |
264 | 267 | if ( $depPollStore->pid === null ) { |
265 | | - return self::fatalError( 'qp_error_missed_dependance_poll', $this->mPollId, $depLink, $depPollId ); |
| 268 | + return self::fatalErrorNoQuote( 'qp_error_missed_dependance_poll', qp_Setup::specialchars( $this->mPollId ), $depLink, qp_Setup::specialchars( $depPollId ) ); |
266 | 269 | } |
267 | 270 | if ( !$depPollStore->loadQuestions() ) { |
268 | | - return self::fatalError( 'qp_error_vote_dependance_poll', $depLink ); |
| 271 | + return self::fatalErrorNoQuote( 'qp_error_vote_dependance_poll', $depLink ); |
269 | 272 | } |
270 | 273 | $depPollStore->setLastUser( $this->username, false ); |
271 | 274 | if ( $depPollStore->loadUserAlreadyVoted() ) { |
— | — | @@ -275,7 +278,7 @@ |
276 | 279 | return true; |
277 | 280 | } else { |
278 | 281 | # there is an non-voted deplink in the chain at some previous level of recursion |
279 | | - return self::fatalError( 'qp_error_vote_dependance_poll', $nonVotedDepLink ); |
| 282 | + return self::fatalErrorNoQuote( 'qp_error_vote_dependance_poll', $nonVotedDepLink ); |
280 | 283 | } |
281 | 284 | } else { |
282 | 285 | return $this->checkDependance( $depPollStore->dependsOn, $nonVotedDepLink ); |
— | — | @@ -284,7 +287,7 @@ |
285 | 288 | # user hasn't voted in current the poll in chain |
286 | 289 | if ( $depPollStore->dependsOn === '' ) { |
287 | 290 | # current element of chain is not voted and furthermore, doesn't depend on any other polls |
288 | | - return self::fatalError( 'qp_error_vote_dependance_poll', $depLink ); |
| 291 | + return self::fatalErrorNoQuote( 'qp_error_vote_dependance_poll', $depLink ); |
289 | 292 | } else { |
290 | 293 | # current element of chain is not voted, BUT it has it's own dependance |
291 | 294 | # so we will check for the most deeply nested poll which hasn't voted, yet |
— | — | @@ -295,7 +298,7 @@ |
296 | 299 | # process poll address errors |
297 | 300 | switch ( $depPollStore ) { |
298 | 301 | case qp_Setup::ERROR_INVALID_ADDRESS : |
299 | | - return self::fatalError( 'qp_error_invalid_dependance_value', $this->mPollId, $dependsOn ); |
| 302 | + return self::fatalErrorQuote( 'qp_error_invalid_dependance_value', $this->mPollId, $dependsOn ); |
300 | 303 | case qp_Setup::ERROR_MISSED_TITLE : |
301 | 304 | $depSplit = self::getPrefixedPollAddress( $dependsOn ); |
302 | 305 | if ( is_array( $depSplit ) ) { |
— | — | @@ -303,9 +306,9 @@ |
304 | 307 | $depTitle = Title::newFromURL( $depTitleStr ); |
305 | 308 | $depTitleStr = $depTitle->getPrefixedText(); |
306 | 309 | $depLink = $this->view->link( $depTitle, $depTitleStr ); |
307 | | - return self::fatalError( 'qp_error_missed_dependance_title', $this->mPollId, $depLink, $depPollId ); |
| 310 | + return self::fatalErrorNoQuote( 'qp_error_missed_dependance_title', qp_Setup::specialchars( $this->mPollId ), $depLink, qp_Setup::specialchars( $depPollId ) ); |
308 | 311 | } else { |
309 | | - return self::fatalError( 'qp_error_invalid_dependance_value', $this->mPollId, $dependsOn ); |
| 312 | + return self::fatalErrorQuote( 'qp_error_invalid_dependance_value', $this->mPollId, $dependsOn ); |
310 | 313 | } |
311 | 314 | default : |
312 | 315 | throw new MWException( __METHOD__ . ' invalid dependance poll store found' ); |
Index: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php |
— | — | @@ -159,7 +159,7 @@ |
160 | 160 | class qp_TextQuestion extends qp_StubQuestion { |
161 | 161 | |
162 | 162 | # regexp for separation of proposal line tokens |
163 | | - var $propCatPattern = null; |
| 163 | + var $propCatPattern; |
164 | 164 | |
165 | 165 | # source "raw" tokens (preg_split) |
166 | 166 | var $rawtokens; |
— | — | @@ -189,7 +189,11 @@ |
190 | 190 | # only proposal parts and category options |
191 | 191 | var $dbtokens = array(); |
192 | 192 | |
193 | | - # list of opening input braces types |
| 193 | + # List of opening input braces types |
| 194 | + # input is html representation of category: |
| 195 | + # brace value 'text' is mapped to input text or to select option; |
| 196 | + # brace values 'radio' and 'checkbox' are mapped to |
| 197 | + # input radio and input checkbox, respectively. |
194 | 198 | var $input_braces_types = array( |
195 | 199 | '<<' => 'text', |
196 | 200 | '<(' => 'radio', |
— | — | @@ -225,16 +229,14 @@ |
226 | 230 | unset( $this->matching_braces[$radio_brace] ); |
227 | 231 | } |
228 | 232 | } |
229 | | - if ( $this->propCatPattern === null ) { |
230 | | - $braces_list = array_map( 'preg_quote', |
231 | | - array_merge( |
232 | | - ( array_values( $this->matching_braces ) ), |
233 | | - array_keys( $this->matching_braces ), |
234 | | - array( '|' ) |
235 | | - ) |
236 | | - ); |
237 | | - $this->propCatPattern = '/(' . implode( '|', $braces_list ) . ')/u'; |
238 | | - } |
| 233 | + $braces_list = array_map( 'preg_quote', |
| 234 | + array_merge( |
| 235 | + ( array_values( $this->matching_braces ) ), |
| 236 | + array_keys( $this->matching_braces ), |
| 237 | + array( '|' ) |
| 238 | + ) |
| 239 | + ); |
| 240 | + $this->propCatPattern = '/(' . implode( '|', $braces_list ) . ')/u'; |
239 | 241 | } |
240 | 242 | |
241 | 243 | /** |
— | — | @@ -252,6 +254,7 @@ |
253 | 255 | * Also, stores checkbox / radio / text answer into the parsed tokens list (propview) |
254 | 256 | */ |
255 | 257 | function loadProposalCategory( qp_TextQuestionOptions $opt, $proposalId, $catId ) { |
| 258 | + global $wgContLang; |
256 | 259 | $name = "q{$this->mQuestionId}p{$proposalId}s{$catId}"; |
257 | 260 | # default value for unanswered category |
258 | 261 | # boolean true "checked" checkbox / radiobutton |
— | — | @@ -261,8 +264,8 @@ |
262 | 265 | if ( $this->poll->mBeingCorrected && $this->mRequest->getVal( $name ) !== null ) { |
263 | 266 | if ( $opt->type === 'text' ) { |
264 | 267 | if ( ( $ta = trim( $this->mRequest->getText( $name ) ) ) != '' ) { |
265 | | - if ( strlen( $ta ) > qp_Setup::MAX_TEXT_ANSWER_LENGTH ) { |
266 | | - $text_answer = substr( $ta, 0, qp_Setup::MAX_TEXT_ANSWER_LENGTH ); |
| 268 | + if ( strlen( $ta ) > qp_Setup::$field_max_len['text_answer'] ) { |
| 269 | + $text_answer = $wgContLang->truncate( $ta, qp_Setup::$field_max_len['text_answer'] , '' ); |
267 | 270 | } else { |
268 | 271 | $text_answer = $ta; |
269 | 272 | } |
— | — | @@ -358,8 +361,13 @@ |
359 | 362 | } |
360 | 363 | } |
361 | 364 | } |
362 | | - # trying to backtrack non-closed braces only these which belong to |
363 | | - # $this->input_braces_types |
| 365 | + } |
| 366 | + |
| 367 | + /** |
| 368 | + * Trying to backtrack non-closed braces only for these which belong to |
| 369 | + * $this->input_braces_types |
| 370 | + */ |
| 371 | + private function backtrackMismatchingBraces() { |
364 | 372 | $brace_keys = array_keys( $this->brace_matches, true ); |
365 | 373 | for ( $i = count( $brace_keys ) - 1; $i >= 0; $i-- ) { |
366 | 374 | $brace_match = &$this->brace_matches[$brace_keys[$i]]; |
— | — | @@ -424,6 +432,7 @@ |
425 | 433 | $this->rawtokens = preg_split( $this->propCatPattern, $raw, -1, PREG_SPLIT_DELIM_CAPTURE ); |
426 | 434 | $matching_closed_brace = ''; |
427 | 435 | $this->findMatchingBraces(); |
| 436 | + $this->backtrackMismatchingBraces(); |
428 | 437 | foreach ( $this->rawtokens as $tkey => $token ) { |
429 | 438 | # $toBeStored == true when current $token has to be stored into |
430 | 439 | # category / proposal list (depending on $opt->isCatDef) |
— | — | @@ -486,7 +495,7 @@ |
487 | 496 | } |
488 | 497 | $proposal_text = serialize( $this->dbtokens ); |
489 | 498 | # build the whole raw DB proposal_text value to check it's maximal length |
490 | | - if ( strlen( qp_QuestionData::getProposalNamePrefix( $prop_name ) . $proposal_text ) > qp_Setup::$proposal_max_length ) { |
| 499 | + if ( strlen( qp_QuestionData::getProposalNamePrefix( $prop_name ) . $proposal_text ) > qp_Setup::$field_max_len['proposal_text'] ) { |
491 | 500 | # too long proposal field to store into the DB |
492 | 501 | # this is very important check for text questions because |
493 | 502 | # category definitions are stored within the proposal text |
Index: trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php |
— | — | @@ -15,6 +15,7 @@ |
16 | 16 | * also may be altered during the poll generation |
17 | 17 | */ |
18 | 18 | function parseBody() { |
| 19 | + global $wgContLang; |
19 | 20 | $this->mProposalPattern = '`^'; |
20 | 21 | foreach ( $this->mCategories as $catDesc ) { |
21 | 22 | $this->mProposalPattern .= '(\[\]|\(\)|<>)'; |
— | — | @@ -76,8 +77,8 @@ |
77 | 78 | if ( $this->poll->mBeingCorrected && $this->mRequest->getVal( $name ) !== null ) { |
78 | 79 | if ( $inputType == 'text' ) { |
79 | 80 | $text_answer = trim( $this->mRequest->getText( $name ) ); |
80 | | - if ( strlen( $text_answer ) > qp_Setup::MAX_TEXT_ANSWER_LENGTH ) { |
81 | | - $text_answer = substr( $text_answer, 0, qp_Setup::MAX_TEXT_ANSWER_LENGTH ); |
| 81 | + if ( strlen( $text_answer ) > qp_Setup::$field_max_len['text_answer'] ) { |
| 82 | + $text_answer = $wgContLang->truncate( $text_answer, qp_Setup::$field_max_len['text_answer'] , '' ); |
82 | 83 | } |
83 | 84 | if ( $text_answer != '' ) { |
84 | 85 | $input_checked = true; |
Index: trunk/extensions/QPoll/qp_user.php |
— | — | @@ -143,7 +143,6 @@ |
144 | 144 | |
145 | 145 | const ERROR_MISSED_TITLE = 1; |
146 | 146 | const ERROR_INVALID_ADDRESS = 2; |
147 | | - const MAX_TEXT_ANSWER_LENGTH = 1024; |
148 | 147 | |
149 | 148 | static $pollTag = 'qpoll'; |
150 | 149 | static $interpTag = 'qpinterpret'; |
— | — | @@ -157,6 +156,9 @@ |
158 | 157 | static $title; // Title instance we got from hook parameter |
159 | 158 | static $user; // User instance we got from hook parameter |
160 | 159 | |
| 160 | + /** |
| 161 | + * The map of question 'type' attribute value to the question's ctrl / view / subtype. |
| 162 | + */ |
161 | 163 | static $questionTypes = array( |
162 | 164 | '[]' => array( |
163 | 165 | 'ctrl' => 'qp_TabularQuestion', |
— | — | @@ -198,9 +200,11 @@ |
199 | 201 | NS_QP_INTERPRETATION_TALK => 'Interpretation_talk' |
200 | 202 | ); |
201 | 203 | |
202 | | - # stores interpretation script line numbers separately for |
203 | | - # every script language (currently only php eval is implemented) |
204 | | - # key is value of <qpinterpret> xml tag 'lang' attribute, value is source line counter |
| 204 | + /** |
| 205 | + * Stores interpretation script line numbers separately for |
| 206 | + * every script language. |
| 207 | + * key is value of <qpinterpret> xml tag 'lang' attribute, value is source line counter |
| 208 | + */ |
205 | 209 | static $scriptLinesCount = array(); |
206 | 210 | |
207 | 211 | /** |
— | — | @@ -215,16 +219,26 @@ |
216 | 220 | public static $cache_control = false; |
217 | 221 | # number of submit attempts allowed (0 or less for infinite number) |
218 | 222 | public static $max_submit_attempts = 0; |
219 | | - # maximal length of question proposal row |
220 | | - # tuned for MySQL ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT |
221 | | - # feel free to increase the value for ROW_FORMAT=DYNAMIC, ROW_FORMAT=COMPRESSED |
222 | | - # however make sure that the whole row will fit into DB page, |
223 | | - # otherwise the performance may decrease |
224 | | - # it is important only for question type="text", where |
225 | | - # proposal text contains serialized array of proposal parts and category fields |
226 | | - public static $proposal_max_length = 768; |
227 | | - # not larger than DB field size, otherwise serialization will be invalid |
228 | | - public static $structured_interpretation_max_length = 65535; |
| 223 | + /** |
| 224 | + * Maximal length of table row is tuned for |
| 225 | + * MySQL ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT . |
| 226 | + * Feel free to increase the value for |
| 227 | + * ROW_FORMAT=DYNAMIC, ROW_FORMAT=COMPRESSED , |
| 228 | + * however make sure that the whole row will fit into DB page. |
| 229 | + * Otherwise the DB performance may decrease. |
| 230 | + */ |
| 231 | + public static $field_max_len = array( |
| 232 | + # not larger than DB field size, otherwise checking of dependance chain will fail: |
| 233 | + 'dependance' => 768, |
| 234 | + 'common_question' => 768, |
| 235 | + # 'proposal_text' length is important for question type="text", where |
| 236 | + # proposal text contains serialized array of proposal parts and category fields: |
| 237 | + 'proposal_text' => 768, |
| 238 | + 'text_answer' => 768, |
| 239 | + 'long_interpretation' => 768, |
| 240 | + # not larger than DB field size, otherwise unserialization will be invalid: |
| 241 | + 'serialized_interpretation' => 65535 |
| 242 | + ); |
229 | 243 | # whether to show short, long, structured interpretation results to end user |
230 | 244 | public static $show_interpretation = array( |
231 | 245 | 'error' => true, |
— | — | @@ -235,11 +249,11 @@ |
236 | 250 | /* end of default configuration settings */ |
237 | 251 | |
238 | 252 | static function entities( $s ) { |
239 | | - return htmlentities( $s, ENT_COMPAT, 'UTF-8' ); |
| 253 | + return htmlentities( $s, ENT_QUOTES, 'UTF-8' ); |
240 | 254 | } |
241 | 255 | |
242 | 256 | static function specialchars( $s ) { |
243 | | - return htmlspecialchars( $s, ENT_COMPAT, 'UTF-8' ); |
| 257 | + return htmlentities( $s, ENT_QUOTES, 'UTF-8' ); |
244 | 258 | } |
245 | 259 | |
246 | 260 | /** |
Index: trunk/extensions/QPoll/interpretation/qp_interpret.php |
— | — | @@ -69,7 +69,7 @@ |
70 | 70 | * @return instance of qp_InterpResult class (interpretation result) |
71 | 71 | */ |
72 | 72 | static function getResult( $interpArticle, $injectVars ) { |
73 | | - global $wgParser; |
| 73 | + global $wgParser, $wgContLang; |
74 | 74 | $matches = array(); |
75 | 75 | # extract <qpinterpret> tags from the article content |
76 | 76 | $wgParser->extractTagsAndParams( array( qp_Setup::$interpTag ), $interpArticle->getRawText(), $matches ); |
— | — | @@ -153,12 +153,17 @@ |
154 | 154 | return $interpResult->setError( wfMsg( 'qp_error_interpretation_no_return' ) ); |
155 | 155 | } |
156 | 156 | $interpResult->structured = isset( $result['structured'] ) ? serialize( $result['structured'] ) : ''; |
157 | | - if ( strlen( $interpResult->structured ) > qp_Setup::$structured_interpretation_max_length ) { |
| 157 | + if ( strlen( $interpResult->structured ) > qp_Setup::$field_max_len['serialized_interpretation'] ) { |
| 158 | + # serialized structured interpretation is too long and |
| 159 | + # this type of interpretation cannot be truncated |
158 | 160 | unset( $interpResult->structured ); |
159 | 161 | return $interpResult->setError( wfMsg( 'qp_error_structured_interpretation_is_too_long' ) ); |
160 | 162 | } |
161 | 163 | $interpResult->short = isset( $result['short'] ) ? strval( $result['short'] ) : ''; |
162 | 164 | $interpResult->long = isset( $result['long'] ) ? strval( $result['long'] ) : ''; |
| 165 | + if ( strlen( $interpResult->long ) > qp_Setup::$field_max_len['long_interpretation'] ) { |
| 166 | + $interpResult->long = $wgContLang->truncate( $interpResult->long, qp_Setup::$field_max_len['long_interpretation'] , '' ); |
| 167 | + } |
163 | 168 | return $interpResult; |
164 | 169 | } |
165 | 170 | |
Index: trunk/extensions/QPoll/view/question/qp_textquestionview.php |
— | — | @@ -43,6 +43,10 @@ |
44 | 44 | */ |
45 | 45 | class qp_TextQuestionViewRow { |
46 | 46 | |
| 47 | + # owner of row ( an insance of qp_TextQuestionView ) |
| 48 | + var $owner; |
| 49 | + # proposal prefix for category tag id generation |
| 50 | + var $id_prefix; |
47 | 51 | # each element of row is real table cell or "cell" with spans, |
48 | 52 | # depending on $this->tabularDisplay value |
49 | 53 | var $row; |
— | — | @@ -51,17 +55,100 @@ |
52 | 56 | # tagarray with current cell builded for row |
53 | 57 | # cell contains one or multiple tags, describing proposal part or category |
54 | 58 | var $cell; |
| 59 | + # current category id |
| 60 | + var $ckey; |
55 | 61 | |
56 | | - function __construct() { |
57 | | - $this->reset(); |
| 62 | + function __construct( qp_TextQuestionView $owner ) { |
| 63 | + $this->owner = $owner; |
| 64 | + $this->reset( '' ); |
58 | 65 | } |
59 | 66 | |
60 | | - function reset() { |
| 67 | + function reset( $id_prefix ) { |
| 68 | + $this->id_prefix = $id_prefix; |
61 | 69 | $this->row = array(); |
62 | 70 | $this->error = array(); |
63 | 71 | $this->cell = array(); |
| 72 | + # category index, starting from 0 |
| 73 | + $this->ckey = 0; |
64 | 74 | } |
65 | 75 | |
| 76 | + function addError( $elem ) { |
| 77 | + $this->cell[] = array( |
| 78 | + '__tag' => 'span', |
| 79 | + 'class' => 'proposalerror', |
| 80 | + $elem->interpError |
| 81 | + ); |
| 82 | + } |
| 83 | + |
| 84 | + function addInput( $elem, $className ) { |
| 85 | + $value = $elem->value; |
| 86 | + # check, whether the definition of category has "pre-filled" value |
| 87 | + # single, non-unanswered, non-empty option is a pre-filled value |
| 88 | + if ( !$elem->unanswered && $elem->value === '' && $elem->options[0] !== '' ) { |
| 89 | + # input text pre-fill |
| 90 | + $value = $elem->options[0]; |
| 91 | + $className .= ' cat_prefilled'; |
| 92 | + } |
| 93 | + $input = array( |
| 94 | + '__tag' => 'input', |
| 95 | + # unique (poll_type,order_id,question,proposal,category) "coordinate" for javascript |
| 96 | + 'id' => "{$this->id_prefix}c{$this->ckey}", |
| 97 | + 'class' => $className, |
| 98 | + 'type' => $elem->type, |
| 99 | + 'name' => $elem->name, |
| 100 | + 'value' => qp_Setup::specialchars( $value ) |
| 101 | + ); |
| 102 | + $this->ckey++; |
| 103 | + if ( $elem->type !== 'text' && $elem->attributes['checked'] === true ) { |
| 104 | + $input['checked'] = 'checked'; |
| 105 | + } |
| 106 | + if ( $elem->type === 'text' && $this->owner->textInputStyle != '' ) { |
| 107 | + # apply poll's textwidth attribute |
| 108 | + $input['style'] = $this->owner->textInputStyle; |
| 109 | + } |
| 110 | + if ( $elem->attributes['width'] !== null ) { |
| 111 | + # apply current category width attribute |
| 112 | + $input['style'] = 'width:' . intval( $elem->attributes['width'] ) . 'em;'; |
| 113 | + } |
| 114 | + $this->cell[] = $input; |
| 115 | + } |
| 116 | + |
| 117 | + function addSelect( $elem, $className ) { |
| 118 | + if ( $elem->options[0] !== '' ) { |
| 119 | + # default element in select/option set always must be empty option |
| 120 | + array_unshift( $elem->options, '' ); |
| 121 | + } |
| 122 | + $html_options = array(); |
| 123 | + foreach ( $elem->options as $option ) { |
| 124 | + $html_option = array( |
| 125 | + '__tag' => 'option', |
| 126 | + 'value' => qp_Setup::entities( $option ), |
| 127 | + qp_Setup::specialchars( $option ) |
| 128 | + ); |
| 129 | + if ( $option === $elem->value ) { |
| 130 | + $html_option['selected'] = 'selected'; |
| 131 | + } |
| 132 | + $html_options[] = $html_option; |
| 133 | + } |
| 134 | + $this->cell[] = array( |
| 135 | + '__tag' => 'select', |
| 136 | + # unique (poll_type,order_id,question,proposal,category) "coordinate" for javascript |
| 137 | + 'id' => "{$this->id_prefix}c{$this->ckey}", |
| 138 | + 'class' => $className, |
| 139 | + 'name' => $elem->name, |
| 140 | + $html_options |
| 141 | + ); |
| 142 | + $this->ckey++; |
| 143 | + } |
| 144 | + |
| 145 | + function addProposalPart( $elem ) { |
| 146 | + $this->cell[] = array( |
| 147 | + '__tag' => 'span', |
| 148 | + 'class' => 'prop_part', |
| 149 | + $this->owner->rtp( $elem ) |
| 150 | + ); |
| 151 | + } |
| 152 | + |
66 | 153 | function addCell() { |
67 | 154 | if ( count( $this->error ) > 0 ) { |
68 | 155 | # merge previous errors to current cell |
— | — | @@ -105,7 +192,7 @@ |
106 | 193 | */ |
107 | 194 | function __construct( &$parser, &$frame, $showResults ) { |
108 | 195 | parent::__construct( $parser, $frame ); |
109 | | - $this->vr = new qp_TextQuestionViewRow(); |
| 196 | + $this->vr = new qp_TextQuestionViewRow( $this ); |
110 | 197 | /* todo: implement showResults */ |
111 | 198 | } |
112 | 199 | |
— | — | @@ -185,12 +272,9 @@ |
186 | 273 | * @return tagarray |
187 | 274 | */ |
188 | 275 | function renderParsedProposal( $pkey, &$viewtokens ) { |
189 | | - # proposal prefix for id generation |
190 | | - $id_prefix = "tx{$this->ctrl->poll->mOrderId}q{$this->ctrl->mQuestionId}p{$pkey}"; |
191 | 276 | $vr = $this->vr; |
192 | | - $vr->reset(); |
193 | | - # category index, starting from 0 |
194 | | - $ckey = 0; |
| 277 | + # proposal prefix for category tag id generation |
| 278 | + $vr->reset( "tx{$this->ctrl->poll->mOrderId}q{$this->ctrl->mQuestionId}p{$pkey}" ); |
195 | 279 | foreach ( $viewtokens as $elem ) { |
196 | 280 | $vr->cell = array(); |
197 | 281 | if ( is_object( $elem ) ) { |
— | — | @@ -204,74 +288,17 @@ |
205 | 289 | $className .= ' cat_noanswer'; |
206 | 290 | } |
207 | 291 | # create view for proposal/category error message |
208 | | - $vr->cell[] = array( |
209 | | - '__tag' => 'span', |
210 | | - 'class' => 'proposalerror', |
211 | | - $elem->interpError |
212 | | - ); |
| 292 | + $vr->addError( $elem ); |
213 | 293 | } |
214 | 294 | # create view for the input options part |
215 | 295 | if ( count( $elem->options ) === 1 ) { |
216 | 296 | # one option produces html text / radio / checkbox input |
217 | | - $value = $elem->value; |
218 | | - # check, whether the definition of category has "pre-filled" value |
219 | | - # single, non-unanswered, non-empty option is a pre-filled value |
220 | | - if ( !$elem->unanswered && $elem->value === '' && $elem->options[0] !== '' ) { |
221 | | - # input text pre-fill |
222 | | - $value = $elem->options[0]; |
223 | | - $className .= ' cat_prefilled'; |
224 | | - } |
225 | | - $input = array( |
226 | | - '__tag' => 'input', |
227 | | - # unique (orderid,question,proposal,category) "coordinate" for javascript |
228 | | - 'id' => "{$id_prefix}c{$ckey}", |
229 | | - 'class' => $className, |
230 | | - 'type' => $elem->type, |
231 | | - 'name' => $elem->name, |
232 | | - 'value' => qp_Setup::specialchars( $value ) |
233 | | - ); |
234 | | - $ckey++; |
235 | | - if ( $elem->type !== 'text' && $elem->attributes['checked'] === true ) { |
236 | | - $input['checked'] = 'checked'; |
237 | | - } |
238 | | - if ( $this->textInputStyle != '' ) { |
239 | | - # apply poll's textwidth attribute |
240 | | - $input['style'] = $this->textInputStyle; |
241 | | - } |
242 | | - if ( $elem->attributes['width'] !== null ) { |
243 | | - # apply current category width attribute |
244 | | - $input['style'] = 'width:' . intval( $elem->attributes['width'] ) . 'em;'; |
245 | | - } |
246 | | - $vr->cell[] = $input; |
| 297 | + $vr->addInput( $elem, $className ); |
247 | 298 | $vr->addCell(); |
248 | 299 | continue; |
249 | 300 | } |
250 | 301 | # multiple options produce html select / options |
251 | | - if ( $elem->options[0] !== '' ) { |
252 | | - # default element in select/option set always must be empty option |
253 | | - array_unshift( $elem->options, '' ); |
254 | | - } |
255 | | - $html_options = array(); |
256 | | - foreach ( $elem->options as $option ) { |
257 | | - $html_option = array( |
258 | | - '__tag' => 'option', |
259 | | - 'value' => qp_Setup::entities( $option ), |
260 | | - qp_Setup::specialchars( $option ) |
261 | | - ); |
262 | | - if ( $option === $elem->value ) { |
263 | | - $html_option['selected'] = 'selected'; |
264 | | - } |
265 | | - $html_options[] = $html_option; |
266 | | - } |
267 | | - $vr->cell[] = array( |
268 | | - '__tag' => 'select', |
269 | | - # unique (poll,question,proposal,category) "coordinate" for javascript |
270 | | - 'id' => "{$id_prefix}c{$ckey}", |
271 | | - 'class' => $className, |
272 | | - 'name' => $elem->name, |
273 | | - $html_options |
274 | | - ); |
275 | | - $ckey++; |
| 302 | + $vr->addSelect( $elem, $className ); |
276 | 303 | $vr->addCell(); |
277 | 304 | } elseif ( isset( $elem->error ) ) { |
278 | 305 | # create view for proposal/category error message |
— | — | @@ -285,11 +312,7 @@ |
286 | 313 | } |
287 | 314 | } else { |
288 | 315 | # create view for the proposal part |
289 | | - $vr->cell[] = array( |
290 | | - '__tag' => 'span', |
291 | | - 'class' => 'prop_part', |
292 | | - $this->rtp( $elem ) |
293 | | - ); |
| 316 | + $vr->addProposalPart( $elem ); |
294 | 317 | $vr->addCell(); |
295 | 318 | } |
296 | 319 | } |