r97419 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r97418‎ | r97419 | r97420 >
Date:14:52, 18 September 2011
Author:questpc
Status:deferred
Tags:
Comment:
Database schema updater class. Now it is possible to update extension tables via standard php update.php. qp_question_proposals.proposal_text maximal length is increased to store serialized proposal/categories tokens of question type=text
Modified paths:
  • /trunk/extensions/QPoll/archives/qpoll_proposal_text_length.src (added) (history)
  • /trunk/extensions/QPoll/ctrl/qp_textquestion.php (modified) (history)
  • /trunk/extensions/QPoll/maintenance (added) (history)
  • /trunk/extensions/QPoll/maintenance/qp_schemaupdater.php (added) (history)
  • /trunk/extensions/QPoll/qp_results.php (deleted) (history)
  • /trunk/extensions/QPoll/qp_user.php (modified) (history)
  • /trunk/extensions/QPoll/specials (added) (history)
  • /trunk/extensions/QPoll/specials/qp_results.php (added) (history)
  • /trunk/extensions/QPoll/specials/qp_special.php (added) (history)
  • /trunk/extensions/QPoll/specials/qp_webinstall.php (added) (history)
  • /trunk/extensions/QPoll/tables/qpoll_0.7.0.src (modified) (history)
  • /trunk/extensions/QPoll/tables/qpoll_random_questions.src (modified) (history)
  • /trunk/extensions/QPoll/tables/qpoll_trunk.sql (modified) (history)

Diff [purge]

Index: trunk/extensions/QPoll/qp_results.php
@@ -1,1128 +0,0 @@
2 -<?php
3 -/**
4 - * ***** BEGIN LICENSE BLOCK *****
5 - * This file is part of QPoll.
6 - * Uses parts of code from Quiz extension (c) 2007 Louis-Rémi BABE. All rights reserved.
7 - *
8 - * QPoll is free software; you can redistribute it and/or modify
9 - * it under the terms of the GNU General Public License as published by
10 - * the Free Software Foundation; either version 2 of the License, or
11 - * (at your option) any later version.
12 - *
13 - * QPoll is distributed in the hope that it will be useful,
14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 - * GNU General Public License for more details.
17 - *
18 - * You should have received a copy of the GNU General Public License
19 - * along with QPoll; if not, write to the Free Software
20 - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 - *
22 - * ***** END LICENSE BLOCK *****
23 - *
24 - * QPoll is a poll tool for MediaWiki.
25 - *
26 - * To activate this extension :
27 - * * Create a new directory named QPoll into the directory "extensions" of MediaWiki.
28 - * * Place the files from the extension archive there.
29 - * * Add this line at the end of your LocalSettings.php file :
30 - * require_once "$IP/extensions/QPoll/qp_user.php";
31 - *
32 - * @version 0.8.0a
33 - * @link http://www.mediawiki.org/wiki/Extension:QPoll
34 - * @author QuestPC <questpc@rambler.ru>
35 - */
36 -
37 -if ( !defined( 'MEDIAWIKI' ) ) {
38 - die( "This file is part of the QPoll extension. It is not a valid entry point.\n" );
39 -}
40 -
41 -class qp_SpecialPage extends SpecialPage {
42 -
43 - static $linker = null;
44 -
45 - public function __construct( $name = '', $restriction = '', $listed = true, $function = false, $file = 'default', $includable = false ) {
46 - if ( self::$linker == null ) {
47 - self::$linker = new Linker();
48 - }
49 - parent::__construct( $name, $restriction, $listed, $function, $file, $includable );
50 - }
51 -
52 - function qpLink( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) {
53 - return self::$linker->link( $target, $text, $customAttribs, $query, $options );
54 - }
55 -
56 -}
57 -
58 -class PollResults extends qp_SpecialPage {
59 -
60 - public function __construct() {
61 - parent::__construct( 'PollResults', 'read' );
62 - # for MW 1.15 (still being used by many customers)
63 - # please do not remove until 2012
64 - if ( self::mediaWikiVersionCompare( '1.16' ) ) {
65 - wfLoadExtensionMessages( 'QPoll' );
66 - }
67 - }
68 -
69 - static $accessPermissions = array( 'read', 'pollresults' );
70 -
71 - static $UsersLink = "";
72 - static $PollsLink = "";
73 -
74 - /**
75 - * Checks if the given user (identified by an object) can execute this special page
76 - * @param $user User: the user to check
77 - * @return Boolean: does the user have permission to view the page?
78 - */
79 - public function userCanExecute( $user ) {
80 - # this fn is used to decide whether to show the page link at Special:Specialpages
81 - foreach ( self::$accessPermissions as $permission ) {
82 - if ( !$user->isAllowed( $permission ) ) {
83 - return false;
84 - }
85 - }
86 - return true;
87 - }
88 -
89 - public function execute( $par ) {
90 - global $wgOut, $wgRequest;
91 - global $wgServer; // "http://www.yourserver.org"
92 - // (should be equal to 'http://'.$_SERVER['SERVER_NAME'])
93 - global $wgScript; // "/subdirectory/of/wiki/index.php"
94 - global $wgUser;
95 -
96 - # check whether the user has sufficient permissions
97 - foreach ( self::$accessPermissions as $permission ) {
98 - if ( !$wgUser->isAllowed( $permission ) ) {
99 - $wgOut->permissionRequired( $permission );
100 - return;
101 - }
102 - }
103 -
104 - if ( class_exists( 'ResourceLoader' ) ) {
105 - # MW 1.17+
106 - // $wgOut->addModules( 'jquery' );
107 - $wgOut->addModules( 'ext.qpoll.special.pollresults' );
108 - } else {
109 - # MW < 1.17
110 - $wgOut->addExtensionStyle( qp_Setup::$ScriptPath . '/clientside/qp_results.css' );
111 - }
112 - if ( self::$UsersLink == "" ) {
113 - self::$UsersLink = $this->qpLink( $this->getTitle(), wfMsg( 'qp_users_list' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'users' ) );
114 - }
115 - if ( self::$PollsLink == "" ) {
116 - self::$PollsLink = $this->qpLink( $this->getTitle(), wfMsg( 'qp_polls_list' ), array( "style" => "font-weight:bold;" ) );
117 - }
118 - $wgOut->addHTML( '<div class="qpoll">' );
119 - $output = "";
120 - $this->setHeaders();
121 - if ( ( $result = $this->checkTables() ) !== true ) {
122 - # tables check failed
123 - $wgOut->addHTML( $result );
124 - return;
125 - }
126 - # normal processing
127 - $cmd = $wgRequest->getVal( 'action' );
128 - if ( $cmd === null ) {
129 - list( $limit, $offset ) = wfCheckLimits();
130 - $qpl = new qp_PollsList();
131 - $qpl->doQuery( $offset, $limit );
132 - } else {
133 - $pid = $wgRequest->getVal( 'id' );
134 - $uid = $wgRequest->getVal( 'uid' );
135 - $question_id = $wgRequest->getVal( 'qid' );
136 - $proposal_id = $wgRequest->getVal( 'pid' );
137 - $cid = $wgRequest->getVal( 'cid' );
138 - switch ( $cmd ) {
139 - case 'stats':
140 - if ( $pid !== null ) {
141 - $pid = intval( $pid );
142 - $output = self::getPollsLink();
143 - $output .= self::getUsersLink();
144 - $output .= $this->showVotes( $pid );
145 - }
146 - break;
147 - case 'stats_xls':
148 - if ( $pid !== null ) {
149 - $pid = intval( $pid );
150 - $this->statsToXLS( $pid );
151 - }
152 - break;
153 - case 'voices_xls':
154 - if ( $pid !== null ) {
155 - $pid = intval( $pid );
156 - $this->voicesToXLS( $pid );
157 - }
158 - break;
159 - case 'uvote':
160 - if ( $pid !== null && $uid !== null ) {
161 - $pid = intval( $pid );
162 - $uid = intval( $uid );
163 - $output = self::getPollsLink();
164 - $output .= self::getUsersLink();
165 - $output .= $this->showUserVote( $pid, $uid );
166 - }
167 - break;
168 - case 'qpcusers':
169 - if ( $pid !== null && $question_id !== null && $proposal_id !== null && $cid !== null ) {
170 - $pid = intval( $pid );
171 - $question_id = intval( $question_id );
172 - $proposal_id = intval( $proposal_id );
173 - $cid = intval( $cid );
174 - list( $limit, $offset ) = wfCheckLimits();
175 - $qucl = new qp_UserCellList( $cmd, $pid, $question_id, $proposal_id, $cid );
176 - $qucl->doQuery( $offset, $limit );
177 - }
178 - break;
179 - case 'users':
180 - case 'users_a':
181 - list( $limit, $offset ) = wfCheckLimits();
182 - $qul = new qp_UsersList( $cmd );
183 - $qul->doQuery( $offset, $limit );
184 - break;
185 - case 'upolls':
186 - case 'nupolls':
187 - if ( $uid !== null ) {
188 - $uid = intval( $uid );
189 - list( $limit, $offset ) = wfCheckLimits();
190 - $qupl = new qp_UserPollsList( $cmd, $uid );
191 - $qupl->doQuery( $offset, $limit );
192 - }
193 - break;
194 - case 'pulist':
195 - case 'npulist':
196 - if ( $pid !== null ) {
197 - $pid = intval( $pid );
198 - list( $limit, $offset ) = wfCheckLimits();
199 - $qpul = new qp_PollUsersList( $cmd, $pid );
200 - $qpul->doQuery( $offset, $limit );
201 - }
202 - break;
203 - }
204 - }
205 - $wgOut->addHTML( $output . '</div>' );
206 - }
207 -
208 - /**
209 - * check for existence of multiple tables in the selected database
210 - */
211 - private function tablesExists( $tableset ) {
212 - $db = & wfGetDB( DB_SLAVE );
213 - $tablesFound = 0;
214 - foreach ( $tableset as &$table ) {
215 - if ( $db->tableExists( $table ) ) {
216 - $tablesFound++;
217 - }
218 - }
219 - return $tablesFound;
220 - }
221 -
222 - /**
223 - * check for the existence of multiple fields in the selected database table
224 - * @param $table table name
225 - * @param $fields field names
226 - */
227 - private function fieldsExists( $table, $fields ) {
228 - $db = & wfGetDB( DB_SLAVE );
229 - if ( !is_array( $fields ) ) {
230 - $fields = array( $fields );
231 - }
232 - foreach ( $fields as $field ) {
233 - if ( !$db->fieldExists( $table, $field ) ) {
234 - return false;
235 - }
236 - }
237 - return true;
238 - }
239 -
240 - /**
241 - * check whether the extension tables exist in DB
242 - * @return true if tables are found, string with error message otherwise
243 - */
244 - private function checkTables() {
245 - $db = & wfGetDB( DB_SLAVE );
246 - $sql_tables = array(
247 - 'qpoll_0.7.0.src' => array(
248 - 'qp_poll_desc',
249 - 'qp_question_desc',
250 - 'qp_question_categories',
251 - 'qp_question_proposals',
252 - 'qp_question_answers',
253 - 'qp_users_polls',
254 - 'qp_users'
255 - ),
256 - 'qpoll_random_questions.src' => array(
257 - 'qp_random_questions'
258 - )
259 - );
260 - $addFields = array(
261 - 'qpoll_interpretation.src' => array(
262 - 'qp_poll_desc' => array( 'interpretation_namespace', 'interpretation_title' ),
263 - 'qp_users_polls' => array( 'attempts', 'short_interpretation', 'long_interpretation' )
264 - )
265 - );
266 - /* check whether the tables were initialized */
267 - $result = true;
268 - $tablesInit = array();
269 - foreach ( $sql_tables as $sourceFile => &$tableset ) {
270 - $tablesFound = $this->tablesExists( $tableset );
271 - if ( $tablesFound == 0 ) {
272 - $tablesInit = array_merge( $tablesInit, $tableset );
273 - # no tables were found, initialize the DB completely with minimal version
274 - if ( ( $r = $db->sourceFile( qp_Setup::$ExtDir . "/tables/{$sourceFile}" ) ) !== true ) {
275 - return $r;
276 - }
277 - } elseif ( $tablesFound != count( $tableset ) ) {
278 - # some tables are missing, serious DB error
279 - return "Some of the extension database tables are missing.<br />Please restore from backup or drop the remaining extension tables, then reload this page.";
280 - }
281 - }
282 - /* start of SQL updates */
283 - $scriptsToRun = $tablesUpgrade = array();
284 - foreach ( $addFields as $script => &$table_list ) {
285 - foreach ( $table_list as $table => &$fields_list ) {
286 - if ( !$this->fieldsExists( $table, $fields_list ) ) {
287 - $scriptsToRun[$script] = true;
288 - if ( array_search( $table, $tablesUpgrade ) === false ) {
289 - array_push( $tablesUpgrade, $table );
290 - }
291 - }
292 - }
293 - }
294 - foreach ( $scriptsToRun as $script => $val ) {
295 - if ( ( $r = $db->sourceFile( qp_Setup::$ExtDir . "/archives/{$script}" ) ) !== true ) {
296 - return $r;
297 - }
298 - }
299 - /* end of SQL updates */
300 - if ( count( $tablesInit ) > 0 ) {
301 - $result = 'The following table(s) were initialized: ' . implode( ', ', $tablesInit ) . '<br />';
302 - }
303 - if ( count( $scriptsToRun ) > 0 ) {
304 - if ( !is_string( $result ) ) {
305 - $result = '';
306 - }
307 - $result = 'The following table(s) were upgraded:' . implode( ', ', $tablesUpgrade ) . '<br />';
308 - }
309 - if ( is_string( $result ) ) {
310 - $result .= 'Please <a href="#" onclick="window.location.reload()">reload</a> this page to view future page edits.';
311 - }
312 - return $result;
313 - }
314 -
315 - private function showAnswerHeader( qp_PollStore $pollStore ) {
316 - $out = '<div style="font-weight:bold;">' . wfMsg( 'qp_results_submit_attempts', intval( $pollStore->attempts ) ) . '</div>';
317 - $interpTitle = $pollStore->getInterpTitle();
318 - if ( $interpTitle === null ) {
319 - $out .= wfMsg( 'qp_poll_has_no_interpretation' );
320 - return $out;
321 - }
322 - /*
323 - # currently, error is not stored in DB, only the vote and long / short interpretations
324 - # todo: is it worth to store it?
325 - if ( $pollStore->interpResult->error != '' ) {
326 - return '<strong class="error">' . qp_Setup::specialchars( $pollStore->interpResult->error ) . '</strong>';
327 - }
328 - */
329 - $out .= '<div class="interp_answer">' . wfMsg( 'qp_results_interpretation_header' ) .
330 - '<div class="interp_answer_body">' . nl2br( wfMsg( 'qp_results_short_interpretation', qp_Setup::specialChars( $pollStore->interpResult->short ) ) ) . '</div>' .
331 - '<div class="interp_answer_body">' . nl2br( wfMsg( 'qp_results_long_interpretation', qp_Setup::specialChars( $pollStore->interpResult->long ) ) ) . '</div>' .
332 - '</div>';
333 - return $out;
334 - }
335 -
336 - private function showUserVote( $pid, $uid ) {
337 - if ( $pid === null || $uid === null ) {
338 - return '';
339 - }
340 - $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
341 - if ( $pollStore->pid === null ) {
342 - return '';
343 - }
344 - $pollStore->loadQuestions();
345 - $userName = $pollStore->getUserName( $uid );
346 - if ( $userName === false ) {
347 - return '';
348 - }
349 - $userTitle = Title::makeTitleSafe( NS_USER, $userName );
350 - $user_link = $this->qpLink( $userTitle, $userName );
351 - $pollStore->setLastUser( $userName, false );
352 - if ( !$pollStore->loadUserVote() ) {
353 - return '';
354 - }
355 - $poll_title = $pollStore->getTitle();
356 - # 'parentheses' key is unavailable in MediaWiki 1.15.x
357 - $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) );
358 - $output = wfMsg( 'qp_browse_to_user', $user_link ) . "<br />\n";
359 - $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n";
360 - $output .= $this->showAnswerHeader( $pollStore );
361 - foreach ( $pollStore->Questions as &$qdata ) {
362 - if ( $pollStore->isUsedQuestion( $qdata->question_id ) ) {
363 - $output .= "<br />\n<b>" . $qdata->question_id . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n";
364 - $qview =
365 - $output .= $qdata->displayUserQuestionVote();
366 - }
367 - }
368 - return $output;
369 - }
370 -
371 - private function showVotes( $pid ) {
372 - $output = "";
373 - if ( $pid !== null ) {
374 - $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
375 - if ( $pollStore->pid !== null ) {
376 - $pollStore->loadQuestions();
377 - $pollStore->loadTotals();
378 - $pollStore->calculateStatistics();
379 - $poll_title = $pollStore->getTitle();
380 - # 'parentheses' is unavailable in 1.14.x
381 - $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) );
382 - $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n";
383 - $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'stats_xls', 'id' => $pid ) ) . "<br />\n";
384 - $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_voices_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'voices_xls', 'id' => $pid ) ) . "<br />\n";
385 - foreach ( $pollStore->Questions as &$qdata ) {
386 - $output .= $qdata->displayQuestionStats( $this, $pid );
387 - }
388 - }
389 - }
390 - return $output;
391 - }
392 -
393 - private function voicesToXLS( $pid ) {
394 - if ( $pid === null ) {
395 - return;
396 - }
397 - $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
398 - if ( $pollStore->pid === null ) {
399 - return;
400 - }
401 - # use default IIS / Apache execution time limit which is much larger than default PHP limit
402 - set_time_limit( 300 );
403 - $poll_id = $pollStore->getPollId();
404 - $pollStore->loadQuestions();
405 - try {
406 - require_once( qp_Setup::$ExtDir . '/Excel/Excel_Writer.php' );
407 - $xls_fname = tempnam( "", ".xls" );
408 - $xls_workbook = new Spreadsheet_Excel_Writer_Workbook( $xls_fname );
409 - $xls_workbook->setVersion( 8 );
410 - $xls_worksheet = &$xls_workbook->addworksheet();
411 - $xls_worksheet->setInputEncoding( "utf-8" );
412 - $xls_worksheet->setPaper( 9 );
413 - $xls_rownum = 0;
414 - $format_heading = &$xls_workbook->addformat( array( 'bold' => 1 ) );
415 - $format_answer = &$xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) );
416 - $format_answer->setAlign( 'left' );
417 - $format_even = &$xls_workbook->addformat( array( 'fgcolor' => 0x2A, 'border' => 1 ) );
418 - $format_even->setAlign( 'left' );
419 - $format_odd = &$xls_workbook->addformat( array( 'fgcolor' => 0x23, 'border' => 1 ) );
420 - $format_odd->setAlign( 'left' );
421 - $first_question = true;
422 - foreach ( $pollStore->Questions as $qkey => &$qdata ) {
423 - if ( $first_question ) {
424 - $totalUsersAnsweredQuestion = $pollStore->totalUsersAnsweredQuestion( $qdata );
425 - $xls_worksheet->write( $xls_rownum, 0, $totalUsersAnsweredQuestion, $format_heading );
426 - $xls_worksheet->write( $xls_rownum++, 1, wfMsgExt( 'qp_users_answered_questions', array( 'parsemag' ), $totalUsersAnsweredQuestion ), $format_heading );
427 - $xls_rownum++;
428 - $first_question = false;
429 - }
430 - $xls_worksheet->write( $xls_rownum, 0, $qdata->question_id, $format_heading );
431 - $xls_worksheet->write( $xls_rownum++, 1, qp_Excel::prepareExcelString( $qdata->CommonQuestion ), $format_heading );
432 - if ( count( $qdata->CategorySpans ) > 0 ) {
433 - $row = array();
434 - foreach ( $qdata->CategorySpans as &$span ) {
435 - $row[] = qp_Excel::prepareExcelString( $span[ "name" ] );
436 - for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
437 - $row[] = "";
438 - }
439 - }
440 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
441 - }
442 - $row = array();
443 - foreach ( $qdata->Categories as &$categ ) {
444 - $row[] = qp_Excel::prepareExcelString( $categ[ "name" ] );
445 - }
446 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
447 -/*
448 - foreach ( $qdata->Percents as $pkey=>&$percent ) {
449 - $xls_worksheet->writerow( $xls_rownum + $pkey, 0, $percent );
450 - }
451 -*/
452 - $voters = array();
453 - $offset = 0;
454 - $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
455 - # iterate through the voters of the current poll (there might be many)
456 - while ( ( $limit = count( $voters = $pollStore->pollVotersPager( $offset ) ) ) > 0 ) {
457 - $uvoices = $pollStore->questionVoicesRange( $qdata->question_id, array_keys( $voters ) );
458 - # get each of proposal votes for current uid
459 - foreach ( $uvoices as $uid => &$pvoices ) {
460 - # output square table of proposal / category answers for each uid in uvoices array
461 - $voicesTable = array();
462 - foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
463 - $row = array_fill( 0, count( $qdata->Categories ), '' );
464 - if ( isset( $pvoices[$propkey] ) ) {
465 - foreach ( $pvoices[$propkey] as $catkey => $text_answer ) {
466 - $row[$catkey] = qp_Excel::prepareExcelString( $text_answer );
467 - }
468 - if ( $spansUsed ) {
469 - foreach ( $row as $catkey => &$cell ) {
470 - $cell = array( 0 => $cell );
471 - if ( $qdata->type == "multipleChoice" ) {
472 - $cell[ "format" ] = ( ( $catkey & 1 ) === 0 ) ? $format_even : $format_odd;
473 - } else {
474 - $cell[ "format" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format_even : $format_odd;
475 - }
476 - }
477 - }
478 - }
479 - $voicesTable[] = $row;
480 - }
481 - qp_Excel::writeFormattedTable( $xls_worksheet, $xls_rownum, 0, $voicesTable, $format_answer );
482 - $row = array();
483 - foreach ( $qdata->ProposalText as $ptext ) {
484 - $row[] = qp_Excel::prepareExcelString( $ptext );
485 - }
486 - $xls_worksheet->writecol( $xls_rownum, count( $qdata->Categories ), $row );
487 - $xls_rownum += count( $qdata->ProposalText ) + 1;
488 - }
489 - $offset += $limit;
490 - }
491 - }
492 - $xls_workbook->close();
493 - header( 'Content-Type: application/x-msexcel; name="' . $poll_id . '.xls"' );
494 - header( 'Content-Disposition: inline; filename="' . $poll_id . '.xls"' );
495 - $fxls = @fopen( $xls_fname, "rb" );
496 - @fpassthru( $fxls );
497 - @unlink( $xls_fname );
498 - exit();
499 - } catch ( Exception $e ) {
500 - die( "Error while exporting poll statistics to Excel table\n" );
501 - }
502 - }
503 -
504 - private function statsToXLS( $pid ) {
505 - if ( $pid === null ) {
506 - return;
507 - }
508 - $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
509 - if ( $pollStore->pid === null ) {
510 - return;
511 - }
512 - $poll_id = $pollStore->getPollId();
513 - $pollStore->loadQuestions();
514 - $pollStore->loadTotals();
515 - $pollStore->calculateStatistics();
516 - try {
517 - require_once( qp_Setup::$ExtDir . '/Excel/Excel_Writer.php' );
518 - $xls_fname = tempnam( "", ".xls" );
519 - $xls_workbook = new Spreadsheet_Excel_Writer_Workbook( $xls_fname );
520 - $xls_workbook->setVersion( 8 );
521 - $xls_worksheet = &$xls_workbook->addworksheet();
522 - $xls_worksheet->setInputEncoding( "utf-8" );
523 - $xls_worksheet->setPaper( 9 );
524 - $xls_rownum = 0;
525 - $percent_num_format = '[Blue]0.0%;[Red]-0.0%;[Black]0%';
526 - $format_heading = &$xls_workbook->addformat( array( 'bold' => 1 ) );
527 - $format_percent = &$xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) );
528 - $format_percent->setAlign( 'left' );
529 - $format_percent->setNumFormat( $percent_num_format );
530 - $format_even = &$xls_workbook->addformat( array( 'fgcolor' => 0x2A, 'border' => 1 ) );
531 - $format_even->setAlign( 'left' );
532 - $format_even->setNumFormat( $percent_num_format );
533 - $format_odd = &$xls_workbook->addformat( array( 'fgcolor' => 0x23, 'border' => 1 ) );
534 - $format_odd->setAlign( 'left' );
535 - $format_odd->setNumFormat( $percent_num_format );
536 - $first_question = true;
537 - foreach ( $pollStore->Questions as $qkey => &$qdata ) {
538 - if ( $first_question ) {
539 - $totalUsersAnsweredQuestion = $pollStore->totalUsersAnsweredQuestion( $qdata );
540 - $xls_worksheet->write( $xls_rownum, 0, $totalUsersAnsweredQuestion, $format_heading );
541 - $xls_worksheet->write( $xls_rownum++, 1, wfMsgExt( 'qp_users_answered_questions', array( 'parsemag' ), $totalUsersAnsweredQuestion ), $format_heading );
542 - $xls_rownum++;
543 - $first_question = false;
544 - }
545 - $xls_worksheet->write( $xls_rownum, 0, $qdata->question_id, $format_heading );
546 - $xls_worksheet->write( $xls_rownum++, 1, qp_Excel::prepareExcelString( $qdata->CommonQuestion ), $format_heading );
547 - if ( count( $qdata->CategorySpans ) > 0 ) {
548 - $row = array();
549 - foreach ( $qdata->CategorySpans as &$span ) {
550 - $row[] = qp_Excel::prepareExcelString( $span[ "name" ] );
551 - for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
552 - $row[] = "";
553 - }
554 - }
555 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
556 - }
557 - $row = array();
558 - foreach ( $qdata->Categories as &$categ ) {
559 - $row[] = qp_Excel::prepareExcelString( $categ[ "name" ] );
560 - }
561 - $xls_worksheet->writerow( $xls_rownum++, 0, $row );
562 -/*
563 - foreach ( $qdata->Percents as $pkey=>&$percent ) {
564 - $xls_worksheet->writerow( $xls_rownum + $pkey, 0, $percent );
565 - }
566 -*/
567 - $percentsTable = array();
568 - $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
569 - foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
570 - if ( isset( $qdata->Percents[ $propkey ] ) ) {
571 - $row = $qdata->Percents[ $propkey ];
572 - foreach ( $row as $catkey => &$cell ) {
573 - $cell = array( 0 => $cell );
574 - if ( $spansUsed ) {
575 - if ( $qdata->type == "multipleChoice" ) {
576 - $cell[ "format" ] = ( ( $catkey & 1 ) === 0 ) ? $format_even : $format_odd;
577 - } else {
578 - $cell[ "format" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format_even : $format_odd;
579 - }
580 - }
581 - }
582 - } else {
583 - $row = array_fill( 0, count( $qdata->Categories ), '' );
584 - }
585 - $percentsTable[] = $row;
586 - }
587 - qp_Excel::writeFormattedTable( $xls_worksheet, $xls_rownum, 0, $percentsTable, $format_percent );
588 - $row = array();
589 - foreach ( $qdata->ProposalText as $ptext ) {
590 - $row[] = qp_Excel::prepareExcelString( $ptext );
591 - }
592 - $xls_worksheet->writecol( $xls_rownum, count( $qdata->Categories ), $row );
593 - $xls_rownum += count( $qdata->ProposalText ) + 1;
594 - }
595 - $xls_workbook->close();
596 - header( 'Content-Type: application/x-msexcel; name="' . $poll_id . '.xls"' );
597 - header( 'Content-Disposition: inline; filename="' . $poll_id . '.xls"' );
598 - $fxls = @fopen( $xls_fname, "rb" );
599 - @fpassthru( $fxls );
600 - @unlink( $xls_fname );
601 - exit();
602 - } catch ( Exception $e ) {
603 - die( "Error while exporting poll statistics to Excel table\n" );
604 - }
605 - }
606 -
607 - static function getUsersLink() {
608 - return "<div>" . self::$UsersLink . "</div>\n";
609 - }
610 -
611 - static function getPollsLink() {
612 - return "<div>" . self::$PollsLink . "</div>\n";
613 - }
614 -
615 -}
616 -
617 -/**
618 - * We do not extend QueryPage anymore because it is purposely made incompatible in 1.18+
619 - * thus, it is much safer to implement a larger subset of pager itself
620 - */
621 -abstract class qp_QueryPage extends qp_SpecialPage {
622 -
623 - var $listoutput = false;
624 -
625 - public function __construct() {
626 - parent::__construct( $this->queryPageName() );
627 - }
628 -
629 - function doQuery( $offset, $limit, $shownavigation = true ) {
630 - global $wgOut, $wgContLang;
631 -
632 - $res = $this->getIntervalResults( $offset, $limit );
633 - $num = count( $res );
634 -
635 - if ( $shownavigation ) {
636 - $wgOut->addHTML( $this->getPageHeader() );
637 -
638 - // if list is empty, display a warning
639 - if ( $num == 0 ) {
640 - $wgOut->addHTML( '<p>' . wfMsgHTML( 'specialpage-empty' ) . '</p>' );
641 - return;
642 - }
643 -
644 - $top = wfShowingResults( $offset, $num );
645 - $wgOut->addHTML( "<p>{$top}\n" );
646 -
647 - // often disable 'next' link when we reach the end
648 - $atend = $num < $limit;
649 -
650 - $sl = wfViewPrevNext( $offset, $limit ,
651 - $wgContLang->specialPage( $this->queryPageName() ),
652 - wfArrayToCGI( $this->linkParameters() ), $atend );
653 - $wgOut->addHTML( "<br />{$sl}</p>\n" );
654 - }
655 - if ( $num > 0 ) {
656 - $s = array();
657 - if ( ! $this->listoutput )
658 - $s[] = $this->openList( $offset );
659 -
660 - foreach ( $res as $r ) {
661 - $format = $this->formatResult( $r );
662 - if ( $format ) {
663 - $s[] = $this->listoutput ? $format : "<li>{$format}</li>\n";
664 - }
665 - }
666 -
667 - if ( ! $this->listoutput )
668 - $s[] = $this->closeList();
669 - $str = $this->listoutput ? $wgContLang->listToText( $s ) : implode( '', $s );
670 - $wgOut->addHTML( $str );
671 - }
672 - if ( $shownavigation ) {
673 - $wgOut->addHTML( "<p>{$sl}</p>\n" );
674 - }
675 - return $num;
676 - }
677 -
678 - /**
679 - * A mutator for $this->listoutput;
680 - *
681 - * @param $bool Boolean
682 - */
683 - function setListoutput( $bool ) {
684 - $this->listoutput = $bool;
685 - }
686 -
687 - function openList( $offset ) {
688 - return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n";
689 - }
690 -
691 - function closeList() {
692 - return "</ol>\n";
693 - }
694 -
695 - /**
696 - * If using extra form wheely-dealies, return a set of parameters here
697 - * as an associative array. They will be encoded and added to the paging
698 - * links (prev/next/lengths).
699 - *
700 - * @return Array
701 - */
702 - function linkParameters() {
703 - return array();
704 - }
705 -
706 - function queryPageName() {
707 - return "PollResults";
708 - }
709 -
710 - function isExpensive() {
711 - return false; // disables caching
712 - }
713 -
714 - function isSyndicated() {
715 - return false;
716 - }
717 -
718 -} /* end of qp_QueryPage class */
719 -
720 -/* list of all users */
721 -class qp_UsersList extends qp_QueryPage {
722 - var $cmd;
723 - var $order_by;
724 - var $different_order_by_link;
725 -
726 - public function __construct( $cmd ) {
727 - parent::__construct();
728 - $this->cmd = $cmd;
729 - if ( $cmd == 'users' ) {
730 - $this->order_by = 'count(pid) DESC, name ASC ';
731 - $this->different_order_by_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_order_by_username' ), array(), array( "action" => "users_a" ) );
732 - } else {
733 - $this->order_by = 'name ';
734 - $this->different_order_by_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_order_by_polls_count' ), array(), array( "action" => "users" ) );
735 - }
736 - }
737 -
738 - function getIntervalResults( $offset, $limit ) {
739 - $result = array();
740 - $db = & wfGetDB( DB_SLAVE );
741 - $qp_users = $db->tableName( 'qp_users' );
742 - $qp_users_polls = $db->tableName( 'qp_users_polls' );
743 - $res = $db->select( "$qp_users_polls qup, $qp_users qu",
744 - array( 'qu.uid as uid', 'name as username', 'count(pid) as pidcount' ),
745 - 'qu.uid=qup.uid',
746 - __METHOD__,
747 - array( 'GROUP BY' => 'qup.uid',
748 - 'ORDER BY' => $this->order_by,
749 - 'OFFSET' => intval( $offset ),
750 - 'LIMIT' => intval( $limit ) )
751 - );
752 - while ( $row = $db->fetchObject( $res ) ) {
753 - $result[] = $row;
754 - }
755 - return $result;
756 - }
757 -
758 - function formatResult( $result ) {
759 - global $wgLang, $wgContLang;
760 - $link = "";
761 - if ( $result !== null ) {
762 - $uid = intval( $result->uid );
763 - $userName = $result->username;
764 - $userTitle = Title::makeTitleSafe( NS_USER, $userName );
765 - $user_link = $this->qpLink( $userTitle, $userName );
766 - $user_polls_link = $this->qpLink( $this->getTitle(), wfMsgExt( 'qp_user_polls_link', array( 'parsemag' ), $result->pidcount, $userName ) , array(), array( "uid" => $uid, "action" => "upolls" ) );
767 - $user_missing_polls_link = $this->qpLink( $this->getTitle(), wfMsgExt( 'qp_user_missing_polls_link', 'parsemag', $userName ) , array(), array( "uid" => $uid, "action" => "nupolls" ) );
768 - $link = $user_link . ': ' . $user_polls_link . ', ' . $user_missing_polls_link;
769 - }
770 - return $link;
771 - }
772 -
773 - function linkParameters() {
774 - $params[ 'action' ] = $this->cmd;
775 - return $params;
776 - }
777 -
778 - function getPageHeader() {
779 - return PollResults::getPollsLink() . '<div class="head">' . wfMsg( 'qp_users_list' ) . '<div>' . $this->different_order_by_link . '</div></div>';
780 - }
781 -
782 -}
783 -
784 -/* list of polls in which selected user (did not|participated) */
785 -class qp_UserPollsList extends qp_QueryPage {
786 - var $uid;
787 - var $inverse;
788 - var $cmd;
789 -
790 - public function __construct( $cmd, $uid ) {
791 - parent::__construct();
792 - $this->uid = intval( $uid );
793 - $this->cmd = $cmd;
794 - $this->inverse = ( $cmd == "nupolls" );
795 - }
796 -
797 - function getPageHeader() {
798 - global $wgLang, $wgContLang;
799 - # fake pollStore to get username by uid: avoid to use this trick as much as possible
800 - $pollStore = new qp_PollStore();
801 - $userName = $pollStore->getUserName( $this->uid );
802 - $db = & wfGetDB( DB_SLAVE );
803 - $res = $db->select(
804 - array( 'qp_users_polls' ),
805 - array( 'count(pid) as pidcount' ),
806 - 'uid=' . $db->addQuotes( $this->uid ),
807 - __METHOD__ );
808 - if ( $row = $db->fetchObject( $res ) ) {
809 - $pidcount = $row->pidcount;
810 - } else {
811 - $pidcount = 0;
812 - }
813 - if ( $userName !== false ) {
814 - $userTitle = Title::makeTitleSafe( NS_USER, $userName );
815 - $user_link = $this->qpLink( $userTitle, $userName );
816 - return PollResults::getPollsLink() . PollResults::getUsersLink() . '<div class="head">' . $user_link . ': ' . ( $this->inverse ? wfMsgExt( 'qp_user_missing_polls_link', 'parsemag', $userName ) : wfMsgExt( 'qp_user_polls_link', array( 'parsemag' ), $pidcount, $userName ) ) . ' ' . '</div>';
817 - }
818 - }
819 -
820 - function getIntervalResults( $offset, $limit ) {
821 - $result = Array();
822 - $db = & wfGetDB( DB_SLAVE );
823 - $page = $db->tableName( 'page' );
824 - $qp_poll_desc = $db->tableName( 'qp_poll_desc' );
825 - $qp_users_polls = $db->tableName( 'qp_users_polls' );
826 - $query = "SELECT pid, page_namespace AS ns, page_title AS title, poll_id ";
827 - $query .= "FROM ($qp_poll_desc, $page) ";
828 - $query .= " WHERE page_id=article_id AND pid " . ( $this->inverse ? "NOT " : "" ) . "IN ";
829 - $query .= "(SELECT pid ";
830 - $query .= "FROM $qp_users_polls ";
831 - $query .= "WHERE uid=" . $db->addQuotes( $this->uid ) . ") ";
832 - $query .= "ORDER BY page_namespace, page_title, poll_id ";
833 - $query .= "LIMIT " . intval( $offset ) . ", " . intval( $limit );
834 - $res = $db->query( $query, __METHOD__ );
835 - while ( $row = $db->fetchObject( $res ) ) {
836 - $result[] = $row;
837 - }
838 - return $result;
839 - }
840 -
841 - function formatResult( $result ) {
842 - global $wgLang, $wgContLang;
843 - $poll_title = Title::makeTitle( $result->ns, $result->title, qp_AbstractPoll::s_getPollTitleFragment( $result->poll_id, '' ) );
844 - $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
845 - $pollname = qp_Setup::specialchars( $result->poll_id );
846 - $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
847 - $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ( $this->inverse ? "_inv" : "" ) ), array(), array( "id" => intval( $result->pid ), "uid" => $this->uid, "action" => "uvote" ) );
848 - $link = wfMsg( 'qp_results_line_qupl', $pagename, $pollname, $voice_link );
849 - return $link;
850 - }
851 -
852 - function linkParameters() {
853 - $params[ "action" ] = $this->cmd;
854 - if ( $this->uid !== null ) {
855 - $params[ "uid" ] = $this->uid;
856 - }
857 - return $params;
858 - }
859 -
860 -}
861 -
862 -/* list of all polls */
863 -class qp_PollsList extends qp_QueryPage {
864 -
865 - function getIntervalResults( $offset, $limit ) {
866 - $result = array();
867 - $db = & wfGetDB( DB_SLAVE );
868 - $res = $db->select(
869 - array( 'page', 'qp_poll_desc' ),
870 - array( 'page_namespace as ns', 'page_title as title', 'pid', 'poll_id', 'order_id' ),
871 - 'page_id=article_id',
872 - __METHOD__,
873 - array( 'ORDER BY' => 'page_namespace, page_title, order_id',
874 - 'OFFSET' => intval( $offset ),
875 - 'LIMIT' => intval( $limit ) )
876 - );
877 - while ( $row = $db->fetchObject( $res ) ) {
878 - $result[] = $row;
879 - }
880 - return $result;
881 - }
882 -
883 - function formatResult( $result ) {
884 - global $wgLang, $wgContLang;
885 - $poll_title = Title::makeTitle( $result->ns, $result->title, qp_AbstractPoll::getPollTitleFragment( $result->poll_id, '' ) );
886 - $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
887 - $pollname = qp_Setup::specialchars( $result->poll_id );
888 - $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
889 - $voices_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_stats_link' ), array(), array( "id" => intval( $result->pid ), "action" => "stats" ) );
890 - $users_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_users_link' ), array(), array( "id" => intval( $result->pid ), "action" => "pulist" ) );
891 - $not_participated_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_not_participated_link' ), array(), array( "id" => intval( $result->pid ), "action" => "npulist" ) );
892 - $link = wfMsg( 'qp_results_line_qpl', $pagename, $pollname, $goto_link, $voices_link, $users_link, $not_participated_link );
893 - return $link;
894 - }
895 -
896 - function getPageHeader() {
897 - return PollResults::getUsersLink() . '<div class="head">' . wfMsg( 'qp_polls_list' ) . '</div>';
898 - }
899 -
900 -}
901 -
902 -/* list of users, (not|participated) in particular poll, defined by pid */
903 -class qp_PollUsersList extends qp_QueryPage {
904 -
905 - var $pid;
906 - var $inverse;
907 - var $cmd;
908 -
909 - public function __construct( $cmd, $pid ) {
910 - parent::__construct();
911 - $this->pid = intval( $pid );
912 - $this->cmd = $cmd;
913 - $this->inverse = ( $cmd == "npulist" );
914 - }
915 -
916 - function getPageHeader() {
917 - global $wgLang, $wgContLang;
918 - $link = "";
919 - $db = & wfGetDB( DB_SLAVE );
920 - $res = $db->select(
921 - array( 'page', 'qp_poll_desc' ),
922 - array( 'page_namespace as ns', 'page_title as title', 'poll_id' ),
923 - 'page_id=article_id and pid=' . $db->addQuotes( $this->pid ),
924 - __METHOD__ );
925 - if ( $row = $db->fetchObject( $res ) ) {
926 - $poll_title = Title::makeTitle( intval( $row->ns ), $row->title, qp_AbstractPoll::getPollTitleFragment( $row->poll_id, '' ) );
927 - $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
928 - $pollname = qp_Setup::specialchars( $row->poll_id );
929 - $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
930 - $spec = wfMsg( 'qp_header_line_qpul', wfMsg( $this->inverse ? 'qp_not_participated_link' : 'qp_users_link' ), $pagename, $pollname );
931 - $head[] = PollResults::getPollsLink();
932 - $head[] = PollResults::getUsersLink();
933 - $head[] = array( '__tag' => 'div', 'class' => 'head', 0 => $spec );
934 - $head[] = ' (' . $goto_link . ')';
935 - $link = qp_Renderer::renderTagArray( $head );
936 - }
937 - return $link;
938 - }
939 -
940 - function getIntervalResults( $offset, $limit ) {
941 - $result = Array();
942 - $db = & wfGetDB( DB_SLAVE );
943 - $qp_users = $db->tableName( 'qp_users' );
944 - $qp_users_polls = $db->tableName( 'qp_users_polls' );
945 - $query = "SELECT uid, name as username ";
946 - $query .= "FROM $qp_users ";
947 - $query .= "WHERE uid " . ( $this->inverse ? "NOT " : "" ) . "IN ";
948 - $query .= "(SELECT uid FROM $qp_users_polls WHERE pid=" . $db->addQuotes( $this->pid ) . ") ";
949 - $query .= "ORDER BY uid ";
950 - $query .= "LIMIT " . intval( $offset ) . ", " . intval( $limit );
951 - $res = $db->query( $query, __METHOD__ );
952 - while ( $row = $db->fetchObject( $res ) ) {
953 - $result[] = $row;
954 - }
955 - return $result;
956 - }
957 -
958 - function formatResult( $result ) {
959 - global $wgLang, $wgContLang;
960 - $link = "";
961 - if ( $result !== null ) {
962 - $uid = intval( $result->uid );
963 - $userName = $result->username;
964 - $userTitle = Title::makeTitleSafe( NS_USER, $userName );
965 - $user_link = $this->qpLink( $userTitle, $userName );
966 - $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ( $this->inverse ? "_inv" : "" ) ), array(), array( "id" => intval( $this->pid ), "uid" => $uid, "action" => "uvote" ) );
967 - $link = wfMsg( 'qp_results_line_qpul', $user_link, $voice_link );
968 - }
969 - return $link;
970 - }
971 -
972 - function linkParameters() {
973 - $params[ "action" ] = $this->cmd;
974 - if ( $this->pid !== null ) {
975 - $params[ "id" ] = $this->pid;
976 - }
977 - return $params;
978 - }
979 -
980 -}
981 -
982 -/* list of users who voted for particular choice of particular proposal of particular question */
983 -class qp_UserCellList extends qp_QueryPage {
984 - var $cmd;
985 - var $pid = null;
986 - var $ns, $title, $poll_id;
987 - var $question_id, $proposal_id, $cat_id;
988 - var $inverse = false;
989 -
990 - public function __construct( $cmd, $pid, $question_id, $proposal_id, $cid ) {
991 - parent::__construct();
992 - $this->cmd = $cmd;
993 - $this->question_id = $question_id;
994 - $this->proposal_id = $proposal_id;
995 - $this->cat_id = $cid;
996 - $db = & wfGetDB( DB_SLAVE );
997 - $qp_poll_desc = $db->tableName( 'qp_poll_desc' );
998 - $page = $db->tableName( 'page' );
999 - $query = "SELECT pid, page_namespace as ns, page_title as title, poll_id ";
1000 - $query .= "FROM ($qp_poll_desc, $page) ";
1001 - $query .= "WHERE page_id=article_id AND pid=" . $db->addQuotes( $pid ) . "";
1002 - $res = $db->query( $query, __METHOD__ );
1003 - if ( $row = $db->fetchObject( $res ) ) {
1004 - $this->pid = intval( $row->pid );
1005 - $this->ns = intval( $row->ns );
1006 - $this->title = $row->title;
1007 - $this->poll_id = $row->poll_id;
1008 - }
1009 - }
1010 -
1011 - function getPageHeader() {
1012 - global $wgLang, $wgContLang;
1013 - $link = "";
1014 - $db = & wfGetDB( DB_SLAVE );
1015 - if ( $this->pid !== null ) {
1016 - $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $this->pid ) );
1017 - if ( $pollStore->pid !== null ) {
1018 - $pollStore->loadQuestions();
1019 - $poll_title = Title::makeTitle( intval( $this->ns ), $this->title, qp_AbstractPoll::getPollTitleFragment( $this->poll_id, '' ) );
1020 - $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
1021 - $pollname = qp_Setup::specialchars( $this->poll_id );
1022 - $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
1023 - $spec = wfMsg( 'qp_header_line_qpul', wfMsg( 'qp_users_link' ), $pagename, $pollname );
1024 - $head[] = PollResults::getPollsLink();
1025 - $head[] = PollResults::getUsersLink();
1026 - $head[] = array( '__tag' => 'div', 'class' => 'head', 0 => $spec );
1027 - # 'parentheses' are unavailable in MW 1.14.x
1028 - $head[] = wfMsg( 'qp_parentheses', $goto_link ) . '<br />';
1029 - $ques_found = false;
1030 - foreach ( $pollStore->Questions as &$ques ) {
1031 - if ( $ques->question_id == $this->question_id ) {
1032 - $ques_found = true;
1033 - break;
1034 - }
1035 - }
1036 - if ( $ques_found ) {
1037 - $qpa = wfMsg( 'qp_header_line_qucl', $this->question_id, qp_Setup::entities( $ques->CommonQuestion ) );
1038 - if ( array_key_exists( $this->cat_id, $ques->Categories ) ) {
1039 - $categ = &$ques->Categories[ $this->cat_id ];
1040 - $proptext = $ques->ProposalText[ $this->proposal_id ];
1041 - $cat_name = $categ['name'];
1042 - if ( array_key_exists( 'spanId', $categ ) ) {
1043 - $cat_name = wfMsg( 'qp_full_category_name', $cat_name, $ques->CategorySpans[ $categ['spanId'] ]['name'] );
1044 - }
1045 - $qpa = wfMsg( 'qp_header_line_qucl',
1046 - $this->question_id,
1047 - qp_Setup::entities( $ques->CommonQuestion ),
1048 - qp_Setup::entities( $proptext ),
1049 - qp_Setup::entities( $cat_name ) ) . '<br />';
1050 - $head[] = array( '__tag' => 'div', 'class' => 'head', 'style' => 'padding-left:2em;', 0 => $qpa );
1051 - $link = qp_Renderer::renderTagArray( $head );
1052 - }
1053 - }
1054 - }
1055 - }
1056 - return $link;
1057 - }
1058 -
1059 - function getIntervalResults( $offset, $limit ) {
1060 - $result = Array();
1061 - $db = & wfGetDB( DB_SLAVE );
1062 - $qp_users = $db->tableName( 'qp_users' );
1063 - $qp_question_answers = $db->tableName( 'qp_question_answers' );
1064 - $query = "SELECT qqa.uid as uid, name as username, text_answer ";
1065 - $query .= "FROM $qp_question_answers qqa ";
1066 - $query .= "INNER JOIN $qp_users qu ON qqa.uid = qu.uid ";
1067 - $query .= "WHERE pid=" . $db->addQuotes( $this->pid ) . " AND question_id=" . $db->addQuotes( $this->question_id ) . " AND proposal_id=" . $db->addQuotes( $this->proposal_id ) . " AND cat_id=" . $db->addQuotes( $this->cat_id ) . " ";
1068 - $query .= "LIMIT " . intval( $offset ) . ", " . intval( $limit );
1069 - $res = $db->query( $query, __METHOD__ );
1070 - while ( $row = $db->fetchObject( $res ) ) {
1071 - $result[] = $row;
1072 - }
1073 - return $result;
1074 - }
1075 -
1076 - function formatResult( $result ) {
1077 - global $wgLang, $wgContLang;
1078 - $link = "";
1079 - if ( $result !== null ) {
1080 - $uid = intval( $result->uid );
1081 - $userName = $result->username;
1082 - $userTitle = Title::makeTitleSafe( NS_USER, $userName );
1083 - $user_link = $this->qpLink( $userTitle, $userName );
1084 - $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ( $this->inverse ? "_inv" : "" ) ), array(), array( "id" => intval( $this->pid ), "uid" => $uid, "action" => "uvote" ) );
1085 - $text_answer = ( $result->text_answer == '' ) ? '' : '<i>' . qp_Setup::entities( $result->text_answer ) . '</i>';
1086 - $link = wfMsg( 'qp_results_line_qucl', $user_link, $voice_link, $text_answer );
1087 - }
1088 - return $link;
1089 - }
1090 -
1091 - function linkParameters() {
1092 - $params[ "action" ] = $this->cmd;
1093 - if ( $this->pid !== null ) {
1094 - $params[ "id" ] = $this->pid;
1095 - $params[ "qid" ] = $this->question_id;
1096 - $params[ "pid" ] = $this->proposal_id;
1097 - $params[ "cid" ] = $this->cat_id;
1098 - }
1099 - return $params;
1100 - }
1101 -
1102 -}
1103 -
1104 -class qp_Excel {
1105 -
1106 - static function prepareExcelString( $s ) {
1107 - if ( preg_match( '`^=.?`', $s ) ) {
1108 - return "'" . $s;
1109 - }
1110 - return $s;
1111 - }
1112 -
1113 - static function writeFormattedTable( $worksheet, $rownum, $colnum, &$table, $format = null ) {
1114 - foreach ( $table as $rnum => &$row ) {
1115 - foreach ( $row as $cnum => &$cell ) {
1116 - if ( is_array( $cell ) ) {
1117 - if ( array_key_exists( "format", $cell ) ) {
1118 - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $cell[ "format" ] );
1119 - } else {
1120 - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $format );
1121 - }
1122 - } else {
1123 - $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell, $format );
1124 - }
1125 - }
1126 - }
1127 - }
1128 -
1129 -}
Index: trunk/extensions/QPoll/maintenance/qp_schemaupdater.php
@@ -0,0 +1,292 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ die( "This file is part of the QPoll extension. It is not a valid entry point.\n" );
 6+}
 7+
 8+/**
 9+ * Updates schema of database
 10+ * Also checks for required core methods
 11+ */
 12+class qp_SchemaUpdater {
 13+
 14+ private static $required_classes_and_methods = array(
 15+ # todo: do not forget to update the list of used core methods
 16+ # except of these which are called immediately in LocalSettings.php
 17+ array(
 18+ # alternatives list
 19+ array( 'Article' => 'doPurge' ),
 20+ array( 'WikiPage' => 'doPurge' )
 21+ ),
 22+ array( 'StubObject' => 'isRealObject' ),
 23+ array( 'Linker' => 'link' ),
 24+ array( 'OutputPage' => 'isPrintable' ),
 25+ array( 'PPFrame' => 'expand' ),
 26+ array( 'Parser' => 'getTitle' ),
 27+ array( 'Parser' => 'setHook' ),
 28+ array( 'Parser' => 'recursiveTagParse' ),
 29+ array( 'ParserCache' => 'getKey' ),
 30+ array( 'ParserCache' => 'singleton' ),
 31+ array( 'Title' => 'getArticleID' ),
 32+ array( 'Title' => 'getPrefixedText' ),
 33+ array( 'Title' => 'makeTitle' ),
 34+ array( 'Title' => 'makeTitleSafe' ),
 35+ array( 'Title' => 'newFromID' ),
 36+ array( 'WebResponse' => 'setCookie' ),
 37+ array( 'Language' => 'lc' ),
 38+ array( 'User' => 'isAnon' )
 39+ );
 40+
 41+ /**
 42+ * Checks whether the required core methods exists
 43+ */
 44+ static function coreRequirements() {
 45+ foreach ( self::$required_classes_and_methods as &$check ) {
 46+ if ( array_key_exists( 0, $check ) ) {
 47+ # process alternatives
 48+ $methodFound = false;
 49+ $methodNames = '';
 50+ foreach ( $check as &$clm ) {
 51+ if ( method_exists( key( $clm ), current( $clm ) ) ) {
 52+ $methodFound = true;
 53+ break;
 54+ }
 55+ if ( $methodNames !== '' ) {
 56+ $methodNames .= ', ';
 57+ }
 58+ $methodNames .= key( $clm ) . '::' . current( $clm );
 59+ }
 60+ if ( !$methodFound ) {
 61+ throw new Exception( "QPoll extension requires one of the following methods to be available: {$methodNames} .<br />\n" .
 62+ "Your version of MediaWiki is incompatible with this extension.\n" );
 63+ }
 64+ } else {
 65+ # process one method
 66+ if ( !method_exists( key( $check ), current( $check ) ) ) {
 67+ throw new Exception( "QPoll extension requires " . key( $check ) . "::" . current( $check ) . " method to be available.<br />\n" .
 68+ "Your version of MediaWiki is incompatible with this extension.\n" );
 69+ }
 70+ }
 71+ }
 72+ if ( !defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
 73+ throw new Exception( "QPoll extension requires ParserFirstCallInit hook.\nPlease upgrade your MediaWiki installation first.\n" );
 74+ }
 75+ }
 76+
 77+ # keys are the paths of script files to run
 78+ private static $scriptsToRun = array();
 79+ # values are the list of initialized tables
 80+ private static $tablesInit = array();
 81+ # keys are the list of upgraded tables
 82+ private static $tablesUpgrade = array();
 83+
 84+ # new tables to add
 85+ static $sql_tables = array(
 86+ 'qpoll_0.7.0.src' => array(
 87+ 'qp_poll_desc',
 88+ 'qp_question_desc',
 89+ 'qp_question_categories',
 90+ 'qp_question_proposals',
 91+ 'qp_question_answers',
 92+ 'qp_users_polls',
 93+ 'qp_users'
 94+ ),
 95+ 'qpoll_random_questions.src' => array(
 96+ 'qp_random_questions'
 97+ )
 98+ );
 99+
 100+ # new fields for already existing tables
 101+ static $addFields = array(
 102+ 'qpoll_interpretation.src' => array(
 103+ 'qp_poll_desc' => array( 'interpretation_namespace', 'interpretation_title' ),
 104+ 'qp_users_polls' => array( 'attempts', 'short_interpretation', 'long_interpretation' )
 105+ )
 106+ );
 107+
 108+ # Table/field names are currently unused, they are only for informational purpose.
 109+ static $modifyFields = array(
 110+ 'qpoll_proposal_text_length.src' => array( 'qp_question_proposals' => array( 'proposal_text' ) )
 111+ );
 112+
 113+ /**
 114+ * check for existence of multiple tables in the selected database
 115+ * @param $tableset array list of DB tables in set
 116+ * @return array with names of non-existing tables in specified list
 117+ */
 118+ private static function tablesExists( $tableset ) {
 119+ $db = & wfGetDB( DB_MASTER );
 120+ $tablesNotFound = array();
 121+ foreach ( $tableset as &$table ) {
 122+ if ( !$db->tableExists( $table ) ) {
 123+ $tablesNotFound[] = $table;
 124+ }
 125+ }
 126+ return $tablesNotFound;
 127+ }
 128+
 129+ /**
 130+ * check for the existence of multiple fields in the selected database table
 131+ * @param $table string table name
 132+ * @param $fields mixed array/string field(s) names
 133+ * @return array with names of non-existing fields in specified table
 134+ */
 135+ private static function fieldsExists( $table, $fields ) {
 136+ $db = & wfGetDB( DB_MASTER );
 137+ if ( !is_array( $fields ) ) {
 138+ $fields = array( $fields );
 139+ }
 140+ $fieldsNotFound = array();
 141+ foreach ( $fields as $field ) {
 142+ if ( !$db->fieldExists( $table, $field ) ) {
 143+ $fieldsNotFound[] = $field;
 144+ }
 145+ }
 146+ return $fieldsNotFound;
 147+ }
 148+
 149+ /**
 150+ * Initializes missed tables grouped by their related sets
 151+ */
 152+ private static function initializeTables() {
 153+ $db = & wfGetDB( DB_MASTER );
 154+ /* check whether the tables were initialized */
 155+ $result = true;
 156+ foreach ( self::$sql_tables as $sourceFile => &$tableset ) {
 157+ $tablesNotFound = self::tablesExists( $tableset );
 158+ if ( count( $tablesNotFound ) === count( $tableset ) ) {
 159+ # all of the tables in set are missing
 160+ self::$tablesInit = array_merge( self::$tablesInit, $tableset );
 161+ # no tables were found, initialize the DB completely with minimal version
 162+ if ( ( $r = $db->sourceFile( qp_Setup::$ExtDir . "/tables/{$sourceFile}" ) ) !== true ) {
 163+ throw new Exception( $r );
 164+ }
 165+ } elseif ( count( $tablesNotFound ) > 0 ) {
 166+ # some tables are missing, serious DB error
 167+ throw new Exception( "The following extension's database tables are missing: " . implode( ', ', $tablesNotFound ) . "<br />Please restore from backup or drop the remaining extension tables, then reload this page." );
 168+ }
 169+ }
 170+ }
 171+
 172+ /**
 173+ * Check for added fields
 174+ */
 175+ static private function fieldsToAdd() {
 176+ foreach ( self::$addFields as $script => &$table_list ) {
 177+ foreach ( $table_list as $table => &$fields_list ) {
 178+ $fieldsNotFound = self::fieldsExists( $table, $fields_list );
 179+ if ( count( $fieldsNotFound ) > 0 ) {
 180+ self::$scriptsToRun[$script] = true;
 181+ self::$tablesUpgrade[$table] = true;
 182+ if ( count( $fieldsNotFound ) !== count( $fields_list ) ) {
 183+ throw new Exception( 'Field(s) (' . implode( ', ', array_diff( $fields_list, $fieldsNotFound ) ) . ') already exist in the table ' . $table . '. Fields cannot be added partially, only the whole set :' . implode( ', ', $fields_list ) );
 184+ }
 185+ }
 186+ }
 187+ }
 188+ }
 189+
 190+ /**
 191+ * Check for modifying existing fields
 192+ *
 193+ * note: Unfortunately, I cannot reliably distinguish tinytext field from
 194+ * text field via MySQLField methods. So, I've made unconditional ALTERing.
 195+ *
 196+ */
 197+ static private function fieldsToModify() {
 198+ /* try to modify existing fields */
 199+ foreach ( self::$modifyFields as $script => &$table_fields ) {
 200+ self::$scriptsToRun[$script] = true;
 201+ foreach ( $table_fields as $table => $fields_list ) {
 202+ $fieldsNotFound = self::fieldsExists( $table, $fields_list );
 203+ if ( count( $fieldsNotFound ) > 0 ) {
 204+ throw new Exception( 'Field(s) (' . implode( ', ', $fieldsNotFound ). ') cannot be modified, because it does not exist' );
 205+ }
 206+ self::$tablesUpgrade[$table] = true;
 207+ }
 208+ }
 209+ }
 210+
 211+ /**
 212+ * Run update scripts on already existing tables
 213+ */
 214+ static private function doUpdates() {
 215+ $db = & wfGetDB( DB_MASTER );
 216+ foreach ( self::$scriptsToRun as $script => $val ) {
 217+ if ( ( $r = $db->sourceFile( qp_Setup::$ExtDir . "/archives/{$script}" ) ) !== true ) {
 218+ throw new Exception( $r );
 219+ }
 220+ }
 221+ }
 222+
 223+ /**
 224+ * Check whether the extension's tables exist in DB;
 225+ * Add/update tables/fields when necessary.
 226+ * @return boolean true if tables are found, string with error message otherwise
 227+ */
 228+ static function checkAndUpdate() {
 229+ try {
 230+ self::coreRequirements();
 231+ self::initializeTables();
 232+ self::fieldsToAdd();
 233+ self::fieldsToModify();
 234+ self::doUpdates();
 235+ } catch ( Exception $e ) {
 236+ return nl2br( $e->getMessage() );
 237+ }
 238+ $result = ''; # great, no errors
 239+ if ( count( self::$tablesInit ) > 0 ) {
 240+ $result = 'The following table(s) were initialized: ' . implode( ', ', self::$tablesInit ) . '<br />';
 241+ }
 242+ if ( count( self::$tablesUpgrade ) > 0 ) {
 243+ $result .= 'The following table(s) were upgraded: ' . implode( ', ', array_keys( self::$tablesUpgrade ) ) . '<br />';
 244+ }
 245+ return $result;
 246+ }
 247+
 248+ /**
 249+ * Updates tables from CLI via php update.php
 250+ */
 251+ public static function onLoadExtensionSchemaUpdates( $updater = null ) {
 252+ global $wgExtNewTables, $wgExtModifiedFields;
 253+ # add tables
 254+ foreach ( self::$sql_tables as $sourceFile => &$tableset ) {
 255+ foreach ( $tableset as $table ) {
 256+ $scriptFile = qp_Setup::$ExtDir . "/tables/{$sourceFile}";
 257+ if ( is_null( $updater ) ) {
 258+ $wgExtNewTables[] = array( $table, $scriptFile );
 259+ } else {
 260+ $updater->addExtensionUpdate( array( 'addTable', $table, $scriptFile, true ) );
 261+ }
 262+ }
 263+ }
 264+ # add fields
 265+ foreach ( self::$addFields as $script => &$table_list ) {
 266+ $scriptFile = qp_Setup::$ExtDir . "/archives/{$script}";
 267+ foreach ( $table_list as $table => &$fields_list ) {
 268+ foreach ( $fields_list as $field ) {
 269+ if ( is_null( $updater ) ) {
 270+ $wgExtNewFields[] = array( $table, $field, $scriptFile );
 271+ } else {
 272+ $updater->addExtensionUpdate( array( 'addField', $table, $field, $scriptFile, true ) );
 273+ }
 274+ }
 275+ }
 276+ }
 277+ # modify fields
 278+ foreach ( self::$modifyFields as $script => &$table_list ) {
 279+ $scriptFile = qp_Setup::$ExtDir . "/archives/{$script}";
 280+ foreach ( $table_list as $table => $fields_list ) {
 281+ foreach ( $fields_list as $field ) {
 282+ if ( is_null( $updater ) ) {
 283+ $wgExtModifiedFields[] = array( $table, $field, $scriptFile );
 284+ } else {
 285+ $updater->addExtensionUpdate( array( 'modifyField', $table, $field, $scriptFile, true ) );
 286+ }
 287+ }
 288+ }
 289+ }
 290+ return true;
 291+ }
 292+
 293+} /* end of qp_SchemaUpdater class */
Property changes on: trunk/extensions/QPoll/maintenance/qp_schemaupdater.php
___________________________________________________________________
Added: svn:eol-style
1294 + native
Index: trunk/extensions/QPoll/specials/qp_special.php
@@ -0,0 +1,161 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of QPoll.
 6+ * Uses parts of code from Quiz extension (c) 2007 Louis-Rémi BABE. All rights reserved.
 7+ *
 8+ * QPoll is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * QPoll is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with QPoll; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * ***** END LICENSE BLOCK *****
 23+ *
 24+ * QPoll is a poll tool for MediaWiki.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named QPoll into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/QPoll/qp_user.php";
 31+ *
 32+ * @version 0.8.0a
 33+ * @link http://www.mediawiki.org/wiki/Extension:QPoll
 34+ * @author QuestPC <questpc@rambler.ru>
 35+ */
 36+
 37+if ( !defined( 'MEDIAWIKI' ) ) {
 38+ die( "This file is part of the QPoll extension. It is not a valid entry point.\n" );
 39+}
 40+
 41+/**
 42+ * A special page with handy built-in Linker
 43+ */
 44+class qp_SpecialPage extends SpecialPage {
 45+
 46+ static $linker = null;
 47+
 48+ public function __construct( $name = '', $restriction = '', $listed = true, $function = false, $file = 'default', $includable = false ) {
 49+ if ( self::$linker == null ) {
 50+ self::$linker = new Linker();
 51+ }
 52+ parent::__construct( $name, $restriction, $listed, $function, $file, $includable );
 53+ }
 54+
 55+ function qpLink( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) {
 56+ return self::$linker->link( $target, $text, $customAttribs, $query, $options );
 57+ }
 58+
 59+} /* end of qp_SpecialPage class */
 60+
 61+/**
 62+ * We do not extend QueryPage anymore because it is purposely made incompatible in 1.18+
 63+ * thus, it is much safer to implement a larger subset of pager itself
 64+ */
 65+abstract class qp_QueryPage extends qp_SpecialPage {
 66+
 67+ var $listoutput = false;
 68+
 69+ public function __construct() {
 70+ parent::__construct( $this->queryPageName() );
 71+ }
 72+
 73+ function doQuery( $offset, $limit, $shownavigation = true ) {
 74+ global $wgOut, $wgContLang;
 75+
 76+ $res = $this->getIntervalResults( $offset, $limit );
 77+ $num = count( $res );
 78+
 79+ if ( $shownavigation ) {
 80+ $wgOut->addHTML( $this->getPageHeader() );
 81+
 82+ // if list is empty, display a warning
 83+ if ( $num == 0 ) {
 84+ $wgOut->addHTML( '<p>' . wfMsgHTML( 'specialpage-empty' ) . '</p>' );
 85+ return;
 86+ }
 87+
 88+ $top = wfShowingResults( $offset, $num );
 89+ $wgOut->addHTML( "<p>{$top}\n" );
 90+
 91+ // often disable 'next' link when we reach the end
 92+ $atend = $num < $limit;
 93+
 94+ $sl = wfViewPrevNext( $offset, $limit ,
 95+ $wgContLang->specialPage( $this->queryPageName() ),
 96+ wfArrayToCGI( $this->linkParameters() ), $atend );
 97+ $wgOut->addHTML( "<br />{$sl}</p>\n" );
 98+ }
 99+ if ( $num > 0 ) {
 100+ $s = array();
 101+ if ( ! $this->listoutput )
 102+ $s[] = $this->openList( $offset );
 103+
 104+ foreach ( $res as $r ) {
 105+ $format = $this->formatResult( $r );
 106+ if ( $format ) {
 107+ $s[] = $this->listoutput ? $format : "<li>{$format}</li>\n";
 108+ }
 109+ }
 110+
 111+ if ( ! $this->listoutput )
 112+ $s[] = $this->closeList();
 113+ $str = $this->listoutput ? $wgContLang->listToText( $s ) : implode( '', $s );
 114+ $wgOut->addHTML( $str );
 115+ }
 116+ if ( $shownavigation ) {
 117+ $wgOut->addHTML( "<p>{$sl}</p>\n" );
 118+ }
 119+ return $num;
 120+ }
 121+
 122+ /**
 123+ * A mutator for $this->listoutput;
 124+ *
 125+ * @param $bool Boolean
 126+ */
 127+ function setListoutput( $bool ) {
 128+ $this->listoutput = $bool;
 129+ }
 130+
 131+ function openList( $offset ) {
 132+ return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n";
 133+ }
 134+
 135+ function closeList() {
 136+ return "</ol>\n";
 137+ }
 138+
 139+ /**
 140+ * If using extra form wheely-dealies, return a set of parameters here
 141+ * as an associative array. They will be encoded and added to the paging
 142+ * links (prev/next/lengths).
 143+ *
 144+ * @return Array
 145+ */
 146+ function linkParameters() {
 147+ return array();
 148+ }
 149+
 150+ function queryPageName() {
 151+ return "PollResults";
 152+ }
 153+
 154+ function isExpensive() {
 155+ return false; // disables caching
 156+ }
 157+
 158+ function isSyndicated() {
 159+ return false;
 160+ }
 161+
 162+} /* end of qp_QueryPage class */
Property changes on: trunk/extensions/QPoll/specials/qp_special.php
___________________________________________________________________
Added: svn:eol-style
1163 + native
Index: trunk/extensions/QPoll/specials/qp_results.php
@@ -0,0 +1,895 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of QPoll.
 6+ * Uses parts of code from Quiz extension (c) 2007 Louis-Rémi BABE. All rights reserved.
 7+ *
 8+ * QPoll is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * QPoll is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with QPoll; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * ***** END LICENSE BLOCK *****
 23+ *
 24+ * QPoll is a poll tool for MediaWiki.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named QPoll into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/QPoll/qp_user.php";
 31+ *
 32+ * @version 0.8.0a
 33+ * @link http://www.mediawiki.org/wiki/Extension:QPoll
 34+ * @author QuestPC <questpc@rambler.ru>
 35+ */
 36+
 37+if ( !defined( 'MEDIAWIKI' ) ) {
 38+ die( "This file is part of the QPoll extension. It is not a valid entry point.\n" );
 39+}
 40+
 41+class PollResults extends qp_SpecialPage {
 42+
 43+ public function __construct() {
 44+ parent::__construct( 'PollResults', 'read' );
 45+ # for MW 1.15 (still being used by many customers)
 46+ # please do not remove until 2012
 47+ if ( qp_Setup::mediaWikiVersionCompare( '1.16' ) ) {
 48+ wfLoadExtensionMessages( 'QPoll' );
 49+ }
 50+ }
 51+
 52+ static $accessPermissions = array( 'read', 'pollresults' );
 53+
 54+ static $UsersLink = "";
 55+ static $PollsLink = "";
 56+
 57+ /**
 58+ * Checks if the given user (identified by an object) can execute this special page
 59+ * @param $user User: the user to check
 60+ * @return Boolean: does the user have permission to view the page?
 61+ */
 62+ public function userCanExecute( $user ) {
 63+ # this fn is used to decide whether to show the page link at Special:Specialpages
 64+ foreach ( self::$accessPermissions as $permission ) {
 65+ if ( !$user->isAllowed( $permission ) ) {
 66+ return false;
 67+ }
 68+ }
 69+ return true;
 70+ }
 71+
 72+ public function execute( $par ) {
 73+ global $wgOut, $wgRequest;
 74+ global $wgServer; // "http://www.yourserver.org"
 75+ // (should be equal to 'http://'.$_SERVER['SERVER_NAME'])
 76+ global $wgScript; // "/subdirectory/of/wiki/index.php"
 77+ global $wgUser;
 78+
 79+ # check whether the user has sufficient permissions
 80+ foreach ( self::$accessPermissions as $permission ) {
 81+ if ( !$wgUser->isAllowed( $permission ) ) {
 82+ $wgOut->permissionRequired( $permission );
 83+ return;
 84+ }
 85+ }
 86+
 87+ if ( class_exists( 'ResourceLoader' ) ) {
 88+ # MW 1.17+
 89+ // $wgOut->addModules( 'jquery' );
 90+ $wgOut->addModules( 'ext.qpoll.special.pollresults' );
 91+ } else {
 92+ # MW < 1.17
 93+ $wgOut->addExtensionStyle( qp_Setup::$ScriptPath . '/clientside/qp_results.css' );
 94+ }
 95+ if ( self::$UsersLink == "" ) {
 96+ self::$UsersLink = $this->qpLink( $this->getTitle(), wfMsg( 'qp_users_list' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'users' ) );
 97+ }
 98+ if ( self::$PollsLink == "" ) {
 99+ self::$PollsLink = $this->qpLink( $this->getTitle(), wfMsg( 'qp_polls_list' ), array( "style" => "font-weight:bold;" ) );
 100+ }
 101+ $wgOut->addHTML( '<div class="qpoll">' );
 102+ $output = "";
 103+ $this->setHeaders();
 104+ $cmd = $wgRequest->getVal( 'action' );
 105+ if ( $cmd === null ) {
 106+ list( $limit, $offset ) = wfCheckLimits();
 107+ $qpl = new qp_PollsList();
 108+ $qpl->doQuery( $offset, $limit );
 109+ } else {
 110+ $pid = $wgRequest->getVal( 'id' );
 111+ $uid = $wgRequest->getVal( 'uid' );
 112+ $question_id = $wgRequest->getVal( 'qid' );
 113+ $proposal_id = $wgRequest->getVal( 'pid' );
 114+ $cid = $wgRequest->getVal( 'cid' );
 115+ switch ( $cmd ) {
 116+ case 'stats':
 117+ if ( $pid !== null ) {
 118+ $pid = intval( $pid );
 119+ $output = self::getPollsLink();
 120+ $output .= self::getUsersLink();
 121+ $output .= $this->showVotes( $pid );
 122+ }
 123+ break;
 124+ case 'stats_xls':
 125+ if ( $pid !== null ) {
 126+ $pid = intval( $pid );
 127+ $this->statsToXLS( $pid );
 128+ }
 129+ break;
 130+ case 'voices_xls':
 131+ if ( $pid !== null ) {
 132+ $pid = intval( $pid );
 133+ $this->voicesToXLS( $pid );
 134+ }
 135+ break;
 136+ case 'uvote':
 137+ if ( $pid !== null && $uid !== null ) {
 138+ $pid = intval( $pid );
 139+ $uid = intval( $uid );
 140+ $output = self::getPollsLink();
 141+ $output .= self::getUsersLink();
 142+ $output .= $this->showUserVote( $pid, $uid );
 143+ }
 144+ break;
 145+ case 'qpcusers':
 146+ if ( $pid !== null && $question_id !== null && $proposal_id !== null && $cid !== null ) {
 147+ $pid = intval( $pid );
 148+ $question_id = intval( $question_id );
 149+ $proposal_id = intval( $proposal_id );
 150+ $cid = intval( $cid );
 151+ list( $limit, $offset ) = wfCheckLimits();
 152+ $qucl = new qp_UserCellList( $cmd, $pid, $question_id, $proposal_id, $cid );
 153+ $qucl->doQuery( $offset, $limit );
 154+ }
 155+ break;
 156+ case 'users':
 157+ case 'users_a':
 158+ list( $limit, $offset ) = wfCheckLimits();
 159+ $qul = new qp_UsersList( $cmd );
 160+ $qul->doQuery( $offset, $limit );
 161+ break;
 162+ case 'upolls':
 163+ case 'nupolls':
 164+ if ( $uid !== null ) {
 165+ $uid = intval( $uid );
 166+ list( $limit, $offset ) = wfCheckLimits();
 167+ $qupl = new qp_UserPollsList( $cmd, $uid );
 168+ $qupl->doQuery( $offset, $limit );
 169+ }
 170+ break;
 171+ case 'pulist':
 172+ case 'npulist':
 173+ if ( $pid !== null ) {
 174+ $pid = intval( $pid );
 175+ list( $limit, $offset ) = wfCheckLimits();
 176+ $qpul = new qp_PollUsersList( $cmd, $pid );
 177+ $qpul->doQuery( $offset, $limit );
 178+ }
 179+ break;
 180+ }
 181+ }
 182+ $wgOut->addHTML( $output . '</div>' );
 183+ }
 184+
 185+ private function showAnswerHeader( qp_PollStore $pollStore ) {
 186+ $out = '<div style="font-weight:bold;">' . wfMsg( 'qp_results_submit_attempts', intval( $pollStore->attempts ) ) . '</div>';
 187+ $interpTitle = $pollStore->getInterpTitle();
 188+ if ( $interpTitle === null ) {
 189+ $out .= wfMsg( 'qp_poll_has_no_interpretation' );
 190+ return $out;
 191+ }
 192+ /*
 193+ # currently, error is not stored in DB, only the vote and long / short interpretations
 194+ # todo: is it worth to store it?
 195+ if ( $pollStore->interpResult->error != '' ) {
 196+ return '<strong class="error">' . qp_Setup::specialchars( $pollStore->interpResult->error ) . '</strong>';
 197+ }
 198+ */
 199+ $out .= '<div class="interp_answer">' . wfMsg( 'qp_results_interpretation_header' ) .
 200+ '<div class="interp_answer_body">' . nl2br( wfMsg( 'qp_results_short_interpretation', qp_Setup::specialChars( $pollStore->interpResult->short ) ) ) . '</div>' .
 201+ '<div class="interp_answer_body">' . nl2br( wfMsg( 'qp_results_long_interpretation', qp_Setup::specialChars( $pollStore->interpResult->long ) ) ) . '</div>' .
 202+ '</div>';
 203+ return $out;
 204+ }
 205+
 206+ private function showUserVote( $pid, $uid ) {
 207+ if ( $pid === null || $uid === null ) {
 208+ return '';
 209+ }
 210+ $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
 211+ if ( $pollStore->pid === null ) {
 212+ return '';
 213+ }
 214+ $pollStore->loadQuestions();
 215+ $userName = $pollStore->getUserName( $uid );
 216+ if ( $userName === false ) {
 217+ return '';
 218+ }
 219+ $userTitle = Title::makeTitleSafe( NS_USER, $userName );
 220+ $user_link = $this->qpLink( $userTitle, $userName );
 221+ $pollStore->setLastUser( $userName, false );
 222+ if ( !$pollStore->loadUserVote() ) {
 223+ return '';
 224+ }
 225+ $poll_title = $pollStore->getTitle();
 226+ # 'parentheses' key is unavailable in MediaWiki 1.15.x
 227+ $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) );
 228+ $output = wfMsg( 'qp_browse_to_user', $user_link ) . "<br />\n";
 229+ $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n";
 230+ $output .= $this->showAnswerHeader( $pollStore );
 231+ foreach ( $pollStore->Questions as &$qdata ) {
 232+ if ( $pollStore->isUsedQuestion( $qdata->question_id ) ) {
 233+ $output .= "<br />\n<b>" . $qdata->question_id . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n";
 234+ $qview =
 235+ $output .= $qdata->displayUserQuestionVote();
 236+ }
 237+ }
 238+ return $output;
 239+ }
 240+
 241+ private function showVotes( $pid ) {
 242+ $output = "";
 243+ if ( $pid !== null ) {
 244+ $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
 245+ if ( $pollStore->pid !== null ) {
 246+ $pollStore->loadQuestions();
 247+ $pollStore->loadTotals();
 248+ $pollStore->calculateStatistics();
 249+ $poll_title = $pollStore->getTitle();
 250+ # 'parentheses' is unavailable in 1.14.x
 251+ $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) );
 252+ $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n";
 253+ $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'stats_xls', 'id' => $pid ) ) . "<br />\n";
 254+ $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_voices_to_xls' ), array( "style" => "font-weight:bold;" ), array( 'action' => 'voices_xls', 'id' => $pid ) ) . "<br />\n";
 255+ foreach ( $pollStore->Questions as &$qdata ) {
 256+ $output .= $qdata->displayQuestionStats( $this, $pid );
 257+ }
 258+ }
 259+ }
 260+ return $output;
 261+ }
 262+
 263+ private function voicesToXLS( $pid ) {
 264+ if ( $pid === null ) {
 265+ return;
 266+ }
 267+ $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
 268+ if ( $pollStore->pid === null ) {
 269+ return;
 270+ }
 271+ # use default IIS / Apache execution time limit which is much larger than default PHP limit
 272+ set_time_limit( 300 );
 273+ $poll_id = $pollStore->getPollId();
 274+ $pollStore->loadQuestions();
 275+ try {
 276+ require_once( qp_Setup::$ExtDir . '/Excel/Excel_Writer.php' );
 277+ $xls_fname = tempnam( "", ".xls" );
 278+ $xls_workbook = new Spreadsheet_Excel_Writer_Workbook( $xls_fname );
 279+ $xls_workbook->setVersion( 8 );
 280+ $xls_worksheet = &$xls_workbook->addworksheet();
 281+ $xls_worksheet->setInputEncoding( "utf-8" );
 282+ $xls_worksheet->setPaper( 9 );
 283+ $xls_rownum = 0;
 284+ $format_heading = &$xls_workbook->addformat( array( 'bold' => 1 ) );
 285+ $format_answer = &$xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) );
 286+ $format_answer->setAlign( 'left' );
 287+ $format_even = &$xls_workbook->addformat( array( 'fgcolor' => 0x2A, 'border' => 1 ) );
 288+ $format_even->setAlign( 'left' );
 289+ $format_odd = &$xls_workbook->addformat( array( 'fgcolor' => 0x23, 'border' => 1 ) );
 290+ $format_odd->setAlign( 'left' );
 291+ $first_question = true;
 292+ foreach ( $pollStore->Questions as $qkey => &$qdata ) {
 293+ if ( $first_question ) {
 294+ $totalUsersAnsweredQuestion = $pollStore->totalUsersAnsweredQuestion( $qdata );
 295+ $xls_worksheet->write( $xls_rownum, 0, $totalUsersAnsweredQuestion, $format_heading );
 296+ $xls_worksheet->write( $xls_rownum++, 1, wfMsgExt( 'qp_users_answered_questions', array( 'parsemag' ), $totalUsersAnsweredQuestion ), $format_heading );
 297+ $xls_rownum++;
 298+ $first_question = false;
 299+ }
 300+ $xls_worksheet->write( $xls_rownum, 0, $qdata->question_id, $format_heading );
 301+ $xls_worksheet->write( $xls_rownum++, 1, qp_Excel::prepareExcelString( $qdata->CommonQuestion ), $format_heading );
 302+ if ( count( $qdata->CategorySpans ) > 0 ) {
 303+ $row = array();
 304+ foreach ( $qdata->CategorySpans as &$span ) {
 305+ $row[] = qp_Excel::prepareExcelString( $span[ "name" ] );
 306+ for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
 307+ $row[] = "";
 308+ }
 309+ }
 310+ $xls_worksheet->writerow( $xls_rownum++, 0, $row );
 311+ }
 312+ $row = array();
 313+ foreach ( $qdata->Categories as &$categ ) {
 314+ $row[] = qp_Excel::prepareExcelString( $categ[ "name" ] );
 315+ }
 316+ $xls_worksheet->writerow( $xls_rownum++, 0, $row );
 317+/*
 318+ foreach ( $qdata->Percents as $pkey=>&$percent ) {
 319+ $xls_worksheet->writerow( $xls_rownum + $pkey, 0, $percent );
 320+ }
 321+*/
 322+ $voters = array();
 323+ $offset = 0;
 324+ $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
 325+ # iterate through the voters of the current poll (there might be many)
 326+ while ( ( $limit = count( $voters = $pollStore->pollVotersPager( $offset ) ) ) > 0 ) {
 327+ $uvoices = $pollStore->questionVoicesRange( $qdata->question_id, array_keys( $voters ) );
 328+ # get each of proposal votes for current uid
 329+ foreach ( $uvoices as $uid => &$pvoices ) {
 330+ # output square table of proposal / category answers for each uid in uvoices array
 331+ $voicesTable = array();
 332+ foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
 333+ $row = array_fill( 0, count( $qdata->Categories ), '' );
 334+ if ( isset( $pvoices[$propkey] ) ) {
 335+ foreach ( $pvoices[$propkey] as $catkey => $text_answer ) {
 336+ $row[$catkey] = qp_Excel::prepareExcelString( $text_answer );
 337+ }
 338+ if ( $spansUsed ) {
 339+ foreach ( $row as $catkey => &$cell ) {
 340+ $cell = array( 0 => $cell );
 341+ if ( $qdata->type == "multipleChoice" ) {
 342+ $cell[ "format" ] = ( ( $catkey & 1 ) === 0 ) ? $format_even : $format_odd;
 343+ } else {
 344+ $cell[ "format" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format_even : $format_odd;
 345+ }
 346+ }
 347+ }
 348+ }
 349+ $voicesTable[] = $row;
 350+ }
 351+ qp_Excel::writeFormattedTable( $xls_worksheet, $xls_rownum, 0, $voicesTable, $format_answer );
 352+ $row = array();
 353+ foreach ( $qdata->ProposalText as $ptext ) {
 354+ $row[] = qp_Excel::prepareExcelString( $ptext );
 355+ }
 356+ $xls_worksheet->writecol( $xls_rownum, count( $qdata->Categories ), $row );
 357+ $xls_rownum += count( $qdata->ProposalText ) + 1;
 358+ }
 359+ $offset += $limit;
 360+ }
 361+ }
 362+ $xls_workbook->close();
 363+ header( 'Content-Type: application/x-msexcel; name="' . $poll_id . '.xls"' );
 364+ header( 'Content-Disposition: inline; filename="' . $poll_id . '.xls"' );
 365+ $fxls = @fopen( $xls_fname, "rb" );
 366+ @fpassthru( $fxls );
 367+ @unlink( $xls_fname );
 368+ exit();
 369+ } catch ( Exception $e ) {
 370+ die( "Error while exporting poll statistics to Excel table\n" );
 371+ }
 372+ }
 373+
 374+ private function statsToXLS( $pid ) {
 375+ if ( $pid === null ) {
 376+ return;
 377+ }
 378+ $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $pid ) );
 379+ if ( $pollStore->pid === null ) {
 380+ return;
 381+ }
 382+ $poll_id = $pollStore->getPollId();
 383+ $pollStore->loadQuestions();
 384+ $pollStore->loadTotals();
 385+ $pollStore->calculateStatistics();
 386+ try {
 387+ require_once( qp_Setup::$ExtDir . '/Excel/Excel_Writer.php' );
 388+ $xls_fname = tempnam( "", ".xls" );
 389+ $xls_workbook = new Spreadsheet_Excel_Writer_Workbook( $xls_fname );
 390+ $xls_workbook->setVersion( 8 );
 391+ $xls_worksheet = &$xls_workbook->addworksheet();
 392+ $xls_worksheet->setInputEncoding( "utf-8" );
 393+ $xls_worksheet->setPaper( 9 );
 394+ $xls_rownum = 0;
 395+ $percent_num_format = '[Blue]0.0%;[Red]-0.0%;[Black]0%';
 396+ $format_heading = &$xls_workbook->addformat( array( 'bold' => 1 ) );
 397+ $format_percent = &$xls_workbook->addformat( array( 'fgcolor' => 0x1A, 'border' => 1 ) );
 398+ $format_percent->setAlign( 'left' );
 399+ $format_percent->setNumFormat( $percent_num_format );
 400+ $format_even = &$xls_workbook->addformat( array( 'fgcolor' => 0x2A, 'border' => 1 ) );
 401+ $format_even->setAlign( 'left' );
 402+ $format_even->setNumFormat( $percent_num_format );
 403+ $format_odd = &$xls_workbook->addformat( array( 'fgcolor' => 0x23, 'border' => 1 ) );
 404+ $format_odd->setAlign( 'left' );
 405+ $format_odd->setNumFormat( $percent_num_format );
 406+ $first_question = true;
 407+ foreach ( $pollStore->Questions as $qkey => &$qdata ) {
 408+ if ( $first_question ) {
 409+ $totalUsersAnsweredQuestion = $pollStore->totalUsersAnsweredQuestion( $qdata );
 410+ $xls_worksheet->write( $xls_rownum, 0, $totalUsersAnsweredQuestion, $format_heading );
 411+ $xls_worksheet->write( $xls_rownum++, 1, wfMsgExt( 'qp_users_answered_questions', array( 'parsemag' ), $totalUsersAnsweredQuestion ), $format_heading );
 412+ $xls_rownum++;
 413+ $first_question = false;
 414+ }
 415+ $xls_worksheet->write( $xls_rownum, 0, $qdata->question_id, $format_heading );
 416+ $xls_worksheet->write( $xls_rownum++, 1, qp_Excel::prepareExcelString( $qdata->CommonQuestion ), $format_heading );
 417+ if ( count( $qdata->CategorySpans ) > 0 ) {
 418+ $row = array();
 419+ foreach ( $qdata->CategorySpans as &$span ) {
 420+ $row[] = qp_Excel::prepareExcelString( $span[ "name" ] );
 421+ for ( $i = 1; $i < $span[ "count" ]; $i++ ) {
 422+ $row[] = "";
 423+ }
 424+ }
 425+ $xls_worksheet->writerow( $xls_rownum++, 0, $row );
 426+ }
 427+ $row = array();
 428+ foreach ( $qdata->Categories as &$categ ) {
 429+ $row[] = qp_Excel::prepareExcelString( $categ[ "name" ] );
 430+ }
 431+ $xls_worksheet->writerow( $xls_rownum++, 0, $row );
 432+/*
 433+ foreach ( $qdata->Percents as $pkey=>&$percent ) {
 434+ $xls_worksheet->writerow( $xls_rownum + $pkey, 0, $percent );
 435+ }
 436+*/
 437+ $percentsTable = array();
 438+ $spansUsed = count( $qdata->CategorySpans ) > 0 || $qdata->type == "multipleChoice";
 439+ foreach ( $qdata->ProposalText as $propkey => &$proposal_text ) {
 440+ if ( isset( $qdata->Percents[ $propkey ] ) ) {
 441+ $row = $qdata->Percents[ $propkey ];
 442+ foreach ( $row as $catkey => &$cell ) {
 443+ $cell = array( 0 => $cell );
 444+ if ( $spansUsed ) {
 445+ if ( $qdata->type == "multipleChoice" ) {
 446+ $cell[ "format" ] = ( ( $catkey & 1 ) === 0 ) ? $format_even : $format_odd;
 447+ } else {
 448+ $cell[ "format" ] = ( ( $qdata->Categories[ $catkey ][ "spanId" ] & 1 ) === 0 ) ? $format_even : $format_odd;
 449+ }
 450+ }
 451+ }
 452+ } else {
 453+ $row = array_fill( 0, count( $qdata->Categories ), '' );
 454+ }
 455+ $percentsTable[] = $row;
 456+ }
 457+ qp_Excel::writeFormattedTable( $xls_worksheet, $xls_rownum, 0, $percentsTable, $format_percent );
 458+ $row = array();
 459+ foreach ( $qdata->ProposalText as $ptext ) {
 460+ $row[] = qp_Excel::prepareExcelString( $ptext );
 461+ }
 462+ $xls_worksheet->writecol( $xls_rownum, count( $qdata->Categories ), $row );
 463+ $xls_rownum += count( $qdata->ProposalText ) + 1;
 464+ }
 465+ $xls_workbook->close();
 466+ header( 'Content-Type: application/x-msexcel; name="' . $poll_id . '.xls"' );
 467+ header( 'Content-Disposition: inline; filename="' . $poll_id . '.xls"' );
 468+ $fxls = @fopen( $xls_fname, "rb" );
 469+ @fpassthru( $fxls );
 470+ @unlink( $xls_fname );
 471+ exit();
 472+ } catch ( Exception $e ) {
 473+ die( "Error while exporting poll statistics to Excel table\n" );
 474+ }
 475+ }
 476+
 477+ static function getUsersLink() {
 478+ return "<div>" . self::$UsersLink . "</div>\n";
 479+ }
 480+
 481+ static function getPollsLink() {
 482+ return "<div>" . self::$PollsLink . "</div>\n";
 483+ }
 484+
 485+}
 486+
 487+/* list of all users */
 488+class qp_UsersList extends qp_QueryPage {
 489+ var $cmd;
 490+ var $order_by;
 491+ var $different_order_by_link;
 492+
 493+ public function __construct( $cmd ) {
 494+ parent::__construct();
 495+ $this->cmd = $cmd;
 496+ if ( $cmd == 'users' ) {
 497+ $this->order_by = 'count(pid) DESC, name ASC ';
 498+ $this->different_order_by_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_order_by_username' ), array(), array( "action" => "users_a" ) );
 499+ } else {
 500+ $this->order_by = 'name ';
 501+ $this->different_order_by_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_order_by_polls_count' ), array(), array( "action" => "users" ) );
 502+ }
 503+ }
 504+
 505+ function getIntervalResults( $offset, $limit ) {
 506+ $result = array();
 507+ $db = & wfGetDB( DB_SLAVE );
 508+ $qp_users = $db->tableName( 'qp_users' );
 509+ $qp_users_polls = $db->tableName( 'qp_users_polls' );
 510+ $res = $db->select( "$qp_users_polls qup, $qp_users qu",
 511+ array( 'qu.uid as uid', 'name as username', 'count(pid) as pidcount' ),
 512+ 'qu.uid=qup.uid',
 513+ __METHOD__,
 514+ array( 'GROUP BY' => 'qup.uid',
 515+ 'ORDER BY' => $this->order_by,
 516+ 'OFFSET' => intval( $offset ),
 517+ 'LIMIT' => intval( $limit ) )
 518+ );
 519+ while ( $row = $db->fetchObject( $res ) ) {
 520+ $result[] = $row;
 521+ }
 522+ return $result;
 523+ }
 524+
 525+ function formatResult( $result ) {
 526+ global $wgLang, $wgContLang;
 527+ $link = "";
 528+ if ( $result !== null ) {
 529+ $uid = intval( $result->uid );
 530+ $userName = $result->username;
 531+ $userTitle = Title::makeTitleSafe( NS_USER, $userName );
 532+ $user_link = $this->qpLink( $userTitle, $userName );
 533+ $user_polls_link = $this->qpLink( $this->getTitle(), wfMsgExt( 'qp_user_polls_link', array( 'parsemag' ), $result->pidcount, $userName ) , array(), array( "uid" => $uid, "action" => "upolls" ) );
 534+ $user_missing_polls_link = $this->qpLink( $this->getTitle(), wfMsgExt( 'qp_user_missing_polls_link', 'parsemag', $userName ) , array(), array( "uid" => $uid, "action" => "nupolls" ) );
 535+ $link = $user_link . ': ' . $user_polls_link . ', ' . $user_missing_polls_link;
 536+ }
 537+ return $link;
 538+ }
 539+
 540+ function linkParameters() {
 541+ $params[ 'action' ] = $this->cmd;
 542+ return $params;
 543+ }
 544+
 545+ function getPageHeader() {
 546+ return PollResults::getPollsLink() . '<div class="head">' . wfMsg( 'qp_users_list' ) . '<div>' . $this->different_order_by_link . '</div></div>';
 547+ }
 548+
 549+}
 550+
 551+/* list of polls in which selected user (did not|participated) */
 552+class qp_UserPollsList extends qp_QueryPage {
 553+ var $uid;
 554+ var $inverse;
 555+ var $cmd;
 556+
 557+ public function __construct( $cmd, $uid ) {
 558+ parent::__construct();
 559+ $this->uid = intval( $uid );
 560+ $this->cmd = $cmd;
 561+ $this->inverse = ( $cmd == "nupolls" );
 562+ }
 563+
 564+ function getPageHeader() {
 565+ global $wgLang, $wgContLang;
 566+ # fake pollStore to get username by uid: avoid to use this trick as much as possible
 567+ $pollStore = new qp_PollStore();
 568+ $userName = $pollStore->getUserName( $this->uid );
 569+ $db = & wfGetDB( DB_SLAVE );
 570+ $res = $db->select(
 571+ array( 'qp_users_polls' ),
 572+ array( 'count(pid) as pidcount' ),
 573+ 'uid=' . $db->addQuotes( $this->uid ),
 574+ __METHOD__ );
 575+ if ( $row = $db->fetchObject( $res ) ) {
 576+ $pidcount = $row->pidcount;
 577+ } else {
 578+ $pidcount = 0;
 579+ }
 580+ if ( $userName !== false ) {
 581+ $userTitle = Title::makeTitleSafe( NS_USER, $userName );
 582+ $user_link = $this->qpLink( $userTitle, $userName );
 583+ return PollResults::getPollsLink() . PollResults::getUsersLink() . '<div class="head">' . $user_link . ': ' . ( $this->inverse ? wfMsgExt( 'qp_user_missing_polls_link', 'parsemag', $userName ) : wfMsgExt( 'qp_user_polls_link', array( 'parsemag' ), $pidcount, $userName ) ) . ' ' . '</div>';
 584+ }
 585+ }
 586+
 587+ function getIntervalResults( $offset, $limit ) {
 588+ $result = Array();
 589+ $db = & wfGetDB( DB_SLAVE );
 590+ $page = $db->tableName( 'page' );
 591+ $qp_poll_desc = $db->tableName( 'qp_poll_desc' );
 592+ $qp_users_polls = $db->tableName( 'qp_users_polls' );
 593+ $query = "SELECT pid, page_namespace AS ns, page_title AS title, poll_id ";
 594+ $query .= "FROM ($qp_poll_desc, $page) ";
 595+ $query .= " WHERE page_id=article_id AND pid " . ( $this->inverse ? "NOT " : "" ) . "IN ";
 596+ $query .= "(SELECT pid ";
 597+ $query .= "FROM $qp_users_polls ";
 598+ $query .= "WHERE uid=" . $db->addQuotes( $this->uid ) . ") ";
 599+ $query .= "ORDER BY page_namespace, page_title, poll_id ";
 600+ $query .= "LIMIT " . intval( $offset ) . ", " . intval( $limit );
 601+ $res = $db->query( $query, __METHOD__ );
 602+ while ( $row = $db->fetchObject( $res ) ) {
 603+ $result[] = $row;
 604+ }
 605+ return $result;
 606+ }
 607+
 608+ function formatResult( $result ) {
 609+ global $wgLang, $wgContLang;
 610+ $poll_title = Title::makeTitle( $result->ns, $result->title, qp_AbstractPoll::s_getPollTitleFragment( $result->poll_id, '' ) );
 611+ $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
 612+ $pollname = qp_Setup::specialchars( $result->poll_id );
 613+ $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
 614+ $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ( $this->inverse ? "_inv" : "" ) ), array(), array( "id" => intval( $result->pid ), "uid" => $this->uid, "action" => "uvote" ) );
 615+ $link = wfMsg( 'qp_results_line_qupl', $pagename, $pollname, $voice_link );
 616+ return $link;
 617+ }
 618+
 619+ function linkParameters() {
 620+ $params[ "action" ] = $this->cmd;
 621+ if ( $this->uid !== null ) {
 622+ $params[ "uid" ] = $this->uid;
 623+ }
 624+ return $params;
 625+ }
 626+
 627+}
 628+
 629+/* list of all polls */
 630+class qp_PollsList extends qp_QueryPage {
 631+
 632+ function getIntervalResults( $offset, $limit ) {
 633+ $result = array();
 634+ $db = & wfGetDB( DB_SLAVE );
 635+ $res = $db->select(
 636+ array( 'page', 'qp_poll_desc' ),
 637+ array( 'page_namespace as ns', 'page_title as title', 'pid', 'poll_id', 'order_id' ),
 638+ 'page_id=article_id',
 639+ __METHOD__,
 640+ array( 'ORDER BY' => 'page_namespace, page_title, order_id',
 641+ 'OFFSET' => intval( $offset ),
 642+ 'LIMIT' => intval( $limit ) )
 643+ );
 644+ while ( $row = $db->fetchObject( $res ) ) {
 645+ $result[] = $row;
 646+ }
 647+ return $result;
 648+ }
 649+
 650+ function formatResult( $result ) {
 651+ global $wgLang, $wgContLang;
 652+ $poll_title = Title::makeTitle( $result->ns, $result->title, qp_AbstractPoll::getPollTitleFragment( $result->poll_id, '' ) );
 653+ $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
 654+ $pollname = qp_Setup::specialchars( $result->poll_id );
 655+ $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
 656+ $voices_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_stats_link' ), array(), array( "id" => intval( $result->pid ), "action" => "stats" ) );
 657+ $users_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_users_link' ), array(), array( "id" => intval( $result->pid ), "action" => "pulist" ) );
 658+ $not_participated_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_not_participated_link' ), array(), array( "id" => intval( $result->pid ), "action" => "npulist" ) );
 659+ $link = wfMsg( 'qp_results_line_qpl', $pagename, $pollname, $goto_link, $voices_link, $users_link, $not_participated_link );
 660+ return $link;
 661+ }
 662+
 663+ function getPageHeader() {
 664+ return PollResults::getUsersLink() . '<div class="head">' . wfMsg( 'qp_polls_list' ) . '</div>';
 665+ }
 666+
 667+}
 668+
 669+/* list of users, (not|participated) in particular poll, defined by pid */
 670+class qp_PollUsersList extends qp_QueryPage {
 671+
 672+ var $pid;
 673+ var $inverse;
 674+ var $cmd;
 675+
 676+ public function __construct( $cmd, $pid ) {
 677+ parent::__construct();
 678+ $this->pid = intval( $pid );
 679+ $this->cmd = $cmd;
 680+ $this->inverse = ( $cmd == "npulist" );
 681+ }
 682+
 683+ function getPageHeader() {
 684+ global $wgLang, $wgContLang;
 685+ $link = "";
 686+ $db = & wfGetDB( DB_SLAVE );
 687+ $res = $db->select(
 688+ array( 'page', 'qp_poll_desc' ),
 689+ array( 'page_namespace as ns', 'page_title as title', 'poll_id' ),
 690+ 'page_id=article_id and pid=' . $db->addQuotes( $this->pid ),
 691+ __METHOD__ );
 692+ if ( $row = $db->fetchObject( $res ) ) {
 693+ $poll_title = Title::makeTitle( intval( $row->ns ), $row->title, qp_AbstractPoll::getPollTitleFragment( $row->poll_id, '' ) );
 694+ $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
 695+ $pollname = qp_Setup::specialchars( $row->poll_id );
 696+ $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
 697+ $spec = wfMsg( 'qp_header_line_qpul', wfMsg( $this->inverse ? 'qp_not_participated_link' : 'qp_users_link' ), $pagename, $pollname );
 698+ $head[] = PollResults::getPollsLink();
 699+ $head[] = PollResults::getUsersLink();
 700+ $head[] = array( '__tag' => 'div', 'class' => 'head', 0 => $spec );
 701+ $head[] = ' (' . $goto_link . ')';
 702+ $link = qp_Renderer::renderTagArray( $head );
 703+ }
 704+ return $link;
 705+ }
 706+
 707+ function getIntervalResults( $offset, $limit ) {
 708+ $result = Array();
 709+ $db = & wfGetDB( DB_SLAVE );
 710+ $qp_users = $db->tableName( 'qp_users' );
 711+ $qp_users_polls = $db->tableName( 'qp_users_polls' );
 712+ $query = "SELECT uid, name as username ";
 713+ $query .= "FROM $qp_users ";
 714+ $query .= "WHERE uid " . ( $this->inverse ? "NOT " : "" ) . "IN ";
 715+ $query .= "(SELECT uid FROM $qp_users_polls WHERE pid=" . $db->addQuotes( $this->pid ) . ") ";
 716+ $query .= "ORDER BY uid ";
 717+ $query .= "LIMIT " . intval( $offset ) . ", " . intval( $limit );
 718+ $res = $db->query( $query, __METHOD__ );
 719+ while ( $row = $db->fetchObject( $res ) ) {
 720+ $result[] = $row;
 721+ }
 722+ return $result;
 723+ }
 724+
 725+ function formatResult( $result ) {
 726+ global $wgLang, $wgContLang;
 727+ $link = "";
 728+ if ( $result !== null ) {
 729+ $uid = intval( $result->uid );
 730+ $userName = $result->username;
 731+ $userTitle = Title::makeTitleSafe( NS_USER, $userName );
 732+ $user_link = $this->qpLink( $userTitle, $userName );
 733+ $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ( $this->inverse ? "_inv" : "" ) ), array(), array( "id" => intval( $this->pid ), "uid" => $uid, "action" => "uvote" ) );
 734+ $link = wfMsg( 'qp_results_line_qpul', $user_link, $voice_link );
 735+ }
 736+ return $link;
 737+ }
 738+
 739+ function linkParameters() {
 740+ $params[ "action" ] = $this->cmd;
 741+ if ( $this->pid !== null ) {
 742+ $params[ "id" ] = $this->pid;
 743+ }
 744+ return $params;
 745+ }
 746+
 747+}
 748+
 749+/* list of users who voted for particular choice of particular proposal of particular question */
 750+class qp_UserCellList extends qp_QueryPage {
 751+ var $cmd;
 752+ var $pid = null;
 753+ var $ns, $title, $poll_id;
 754+ var $question_id, $proposal_id, $cat_id;
 755+ var $inverse = false;
 756+
 757+ public function __construct( $cmd, $pid, $question_id, $proposal_id, $cid ) {
 758+ parent::__construct();
 759+ $this->cmd = $cmd;
 760+ $this->question_id = $question_id;
 761+ $this->proposal_id = $proposal_id;
 762+ $this->cat_id = $cid;
 763+ $db = & wfGetDB( DB_SLAVE );
 764+ $qp_poll_desc = $db->tableName( 'qp_poll_desc' );
 765+ $page = $db->tableName( 'page' );
 766+ $query = "SELECT pid, page_namespace as ns, page_title as title, poll_id ";
 767+ $query .= "FROM ($qp_poll_desc, $page) ";
 768+ $query .= "WHERE page_id=article_id AND pid=" . $db->addQuotes( $pid ) . "";
 769+ $res = $db->query( $query, __METHOD__ );
 770+ if ( $row = $db->fetchObject( $res ) ) {
 771+ $this->pid = intval( $row->pid );
 772+ $this->ns = intval( $row->ns );
 773+ $this->title = $row->title;
 774+ $this->poll_id = $row->poll_id;
 775+ }
 776+ }
 777+
 778+ function getPageHeader() {
 779+ global $wgLang, $wgContLang;
 780+ $link = "";
 781+ $db = & wfGetDB( DB_SLAVE );
 782+ if ( $this->pid !== null ) {
 783+ $pollStore = new qp_PollStore( array( 'from' => 'pid', 'pid' => $this->pid ) );
 784+ if ( $pollStore->pid !== null ) {
 785+ $pollStore->loadQuestions();
 786+ $poll_title = Title::makeTitle( intval( $this->ns ), $this->title, qp_AbstractPoll::getPollTitleFragment( $this->poll_id, '' ) );
 787+ $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) );
 788+ $pollname = qp_Setup::specialchars( $this->poll_id );
 789+ $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) );
 790+ $spec = wfMsg( 'qp_header_line_qpul', wfMsg( 'qp_users_link' ), $pagename, $pollname );
 791+ $head[] = PollResults::getPollsLink();
 792+ $head[] = PollResults::getUsersLink();
 793+ $head[] = array( '__tag' => 'div', 'class' => 'head', 0 => $spec );
 794+ # 'parentheses' are unavailable in MW 1.14.x
 795+ $head[] = wfMsg( 'qp_parentheses', $goto_link ) . '<br />';
 796+ $ques_found = false;
 797+ foreach ( $pollStore->Questions as &$ques ) {
 798+ if ( $ques->question_id == $this->question_id ) {
 799+ $ques_found = true;
 800+ break;
 801+ }
 802+ }
 803+ if ( $ques_found ) {
 804+ $qpa = wfMsg( 'qp_header_line_qucl', $this->question_id, qp_Setup::entities( $ques->CommonQuestion ) );
 805+ if ( array_key_exists( $this->cat_id, $ques->Categories ) ) {
 806+ $categ = &$ques->Categories[ $this->cat_id ];
 807+ $proptext = $ques->ProposalText[ $this->proposal_id ];
 808+ $cat_name = $categ['name'];
 809+ if ( array_key_exists( 'spanId', $categ ) ) {
 810+ $cat_name = wfMsg( 'qp_full_category_name', $cat_name, $ques->CategorySpans[ $categ['spanId'] ]['name'] );
 811+ }
 812+ $qpa = wfMsg( 'qp_header_line_qucl',
 813+ $this->question_id,
 814+ qp_Setup::entities( $ques->CommonQuestion ),
 815+ qp_Setup::entities( $proptext ),
 816+ qp_Setup::entities( $cat_name ) ) . '<br />';
 817+ $head[] = array( '__tag' => 'div', 'class' => 'head', 'style' => 'padding-left:2em;', 0 => $qpa );
 818+ $link = qp_Renderer::renderTagArray( $head );
 819+ }
 820+ }
 821+ }
 822+ }
 823+ return $link;
 824+ }
 825+
 826+ function getIntervalResults( $offset, $limit ) {
 827+ $result = Array();
 828+ $db = & wfGetDB( DB_SLAVE );
 829+ $qp_users = $db->tableName( 'qp_users' );
 830+ $qp_question_answers = $db->tableName( 'qp_question_answers' );
 831+ $query = "SELECT qqa.uid as uid, name as username, text_answer ";
 832+ $query .= "FROM $qp_question_answers qqa ";
 833+ $query .= "INNER JOIN $qp_users qu ON qqa.uid = qu.uid ";
 834+ $query .= "WHERE pid=" . $db->addQuotes( $this->pid ) . " AND question_id=" . $db->addQuotes( $this->question_id ) . " AND proposal_id=" . $db->addQuotes( $this->proposal_id ) . " AND cat_id=" . $db->addQuotes( $this->cat_id ) . " ";
 835+ $query .= "LIMIT " . intval( $offset ) . ", " . intval( $limit );
 836+ $res = $db->query( $query, __METHOD__ );
 837+ while ( $row = $db->fetchObject( $res ) ) {
 838+ $result[] = $row;
 839+ }
 840+ return $result;
 841+ }
 842+
 843+ function formatResult( $result ) {
 844+ global $wgLang, $wgContLang;
 845+ $link = "";
 846+ if ( $result !== null ) {
 847+ $uid = intval( $result->uid );
 848+ $userName = $result->username;
 849+ $userTitle = Title::makeTitleSafe( NS_USER, $userName );
 850+ $user_link = $this->qpLink( $userTitle, $userName );
 851+ $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ( $this->inverse ? "_inv" : "" ) ), array(), array( "id" => intval( $this->pid ), "uid" => $uid, "action" => "uvote" ) );
 852+ $text_answer = ( $result->text_answer == '' ) ? '' : '<i>' . qp_Setup::entities( $result->text_answer ) . '</i>';
 853+ $link = wfMsg( 'qp_results_line_qucl', $user_link, $voice_link, $text_answer );
 854+ }
 855+ return $link;
 856+ }
 857+
 858+ function linkParameters() {
 859+ $params[ "action" ] = $this->cmd;
 860+ if ( $this->pid !== null ) {
 861+ $params[ "id" ] = $this->pid;
 862+ $params[ "qid" ] = $this->question_id;
 863+ $params[ "pid" ] = $this->proposal_id;
 864+ $params[ "cid" ] = $this->cat_id;
 865+ }
 866+ return $params;
 867+ }
 868+
 869+}
 870+
 871+class qp_Excel {
 872+
 873+ static function prepareExcelString( $s ) {
 874+ if ( preg_match( '`^=.?`', $s ) ) {
 875+ return "'" . $s;
 876+ }
 877+ return $s;
 878+ }
 879+
 880+ static function writeFormattedTable( $worksheet, $rownum, $colnum, &$table, $format = null ) {
 881+ foreach ( $table as $rnum => &$row ) {
 882+ foreach ( $row as $cnum => &$cell ) {
 883+ if ( is_array( $cell ) ) {
 884+ if ( array_key_exists( "format", $cell ) ) {
 885+ $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $cell[ "format" ] );
 886+ } else {
 887+ $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell[ 0 ], $format );
 888+ }
 889+ } else {
 890+ $worksheet->write( $rownum + $rnum, $colnum + $cnum, $cell, $format );
 891+ }
 892+ }
 893+ }
 894+ }
 895+
 896+}
Property changes on: trunk/extensions/QPoll/specials/qp_results.php
___________________________________________________________________
Added: svn:eol-style
1897 + native
Index: trunk/extensions/QPoll/specials/qp_webinstall.php
@@ -0,0 +1,31 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ die( "This file is part of the QPoll extension. It is not a valid entry point.\n" );
 6+}
 7+
 8+/**
 9+ * Installs/updates DB schema for the people who do not have shell access
 10+ * (not being able to run maintenance scripts)
 11+ */
 12+class qp_WebInstall extends qp_SpecialPage {
 13+
 14+ private $allowed_groups = array( 'sysop', 'bureaucrat' );
 15+
 16+ public function __construct() {
 17+ parent::__construct( 'QPollWebInstall', 'read' );
 18+ }
 19+
 20+ public function execute( $par ) {
 21+ global $wgOut, $wgUser;
 22+
 23+ # only sysops and bureaucrats can update the DB
 24+ if ( count( array_intersect( $this->allowed_groups, $wgUser->getEffectiveGroups() ) ) == 0 ) {
 25+ $wgOut->addHTML( 'You have to be a member of the following group(s) to perform web install:' . implode( ', ', $this->allowed_groups ) );
 26+ return;
 27+ }
 28+ # display update result
 29+ $wgOut->addHTML( qp_SchemaUpdater::checkAndUpdate() );
 30+ }
 31+
 32+} /* end of qp_WebInstall class */
Property changes on: trunk/extensions/QPoll/specials/qp_webinstall.php
___________________________________________________________________
Added: svn:eol-style
133 + native
Index: trunk/extensions/QPoll/archives/qpoll_proposal_text_length.src
@@ -0,0 +1,3 @@
 2+-- allow longer length of proposal text
 3+ALTER TABLE /*$wgDBprefix*/qp_question_proposals
 4+ MODIFY COLUMN proposal_text text NOT NULL;
Index: trunk/extensions/QPoll/ctrl/qp_textquestion.php
@@ -86,9 +86,12 @@
8787 function closeCategory() {
8888 $this->isCatDef = false;
8989 # prepare new category input choice (text questions have no category names)
90 - $this->input_options = array_unique( $this->input_options, SORT_STRING );
91 - # make sure elements keys are consequitive starting from 0
92 - sort( $this->input_options, SORT_STRING );
 90+ $unique_options = array_unique( $this->input_options, SORT_STRING );
 91+ $this->input_options = array();
 92+ foreach ( $unique_options as $option ) {
 93+ # make sure unique elements keys are consequitive starting from 0
 94+ $this->input_options[] = $option;
 95+ }
9396 }
9497
9598 } /* end of qp_TextQuestionOptions class */
@@ -102,8 +105,6 @@
103106 */
104107 class qp_TextQuestion extends qp_StubQuestion {
105108
106 - # not longer than qp_question_proposals.proposal_text (currently, tinytext)
107 - const MAX_PROPOSAL_LENGTH = 255;
108109 const PROP_CAT_PATTERN = '`(<<|>>|{{|}}|\[\[|\]\]|\|)`u';
109110
110111 # $viewtokens is an instance of qp_TextQuestionViewTokens
@@ -241,7 +242,7 @@
242243 # todo: this is the explanary line, it is not real proposal
243244 $this->viewtokens->prependErrorToken( wfMsg( 'qp_error_too_few_categories' ), 'error' );
244245 }
245 - if ( strlen( $proposal_text = serialize( $this->dbtokens ) ) > self::MAX_PROPOSAL_LENGTH ) {
 246+ if ( strlen( $proposal_text = serialize( $this->dbtokens ) ) > qp_Setup::$proposal_max_length ) {
246247 # too long proposal field to store into the DB
247248 # this is very important check for text questions because
248249 # category definitions are stored within the proposal text
Index: trunk/extensions/QPoll/qp_user.php
@@ -51,7 +51,7 @@
5252 'path' => __FILE__,
5353 'name' => 'QPoll',
5454 'version' => '0.8.0a',
55 - 'author' => 'QuestPC',
 55+ 'author' => 'Dmitriy Sintsov',
5656 'url' => 'http://www.mediawiki.org/wiki/Extension:QPoll',
5757 'descriptionmsg' => 'qp_desc',
5858 );
@@ -59,7 +59,7 @@
6060 'path' => __FILE__,
6161 'name' => 'QPoll results page',
6262 'version' => '0.8.0a',
63 - 'author' => 'QuestPC',
 63+ 'author' => 'Dmitriy Sintsov',
6464 'url' => 'http://www.mediawiki.org/wiki/Extension:QPoll',
6565 'descriptionmsg' => 'qp_desc-sp',
6666 );
@@ -134,6 +134,7 @@
135135 static $ScriptPath; // apache virtual path
136136 static $messagesLoaded = false; // check whether the extension's localized messages are loaded
137137 static $article; // Article instance we got from hook parameter
 138+ static $title; // Title instance we got from hook parameter
138139 static $user; // User instance we got from hook parameter
139140
140141 static $questionTypes = array(
@@ -187,6 +188,14 @@
188189 public static $cache_control = false;
189190 # number of submit attempts allowed (0 or less for infinite number)
190191 public static $max_submit_attempts = 0;
 192+ # maximal length of question proposal row
 193+ # tuned for MySQL ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
 194+ # feel free to increase the value for ROW_FORMAT=DYNAMIC, ROW_FORMAT=COMPRESSED
 195+ # however make sure that the whole row will fit into DB page,
 196+ # otherwise the performance may decrease
 197+ # it is important only for question type="text", where
 198+ # proposal text contains serialized array of proposal parts and category fields
 199+ public static $proposal_max_length = 768;
191200 /* end of default configuration settings */
192201
193202 static function entities( $s ) {
@@ -197,34 +206,6 @@
198207 return htmlspecialchars( $s, ENT_COMPAT, 'UTF-8' );
199208 }
200209
201 - static function coreRequirements() {
202 - $required_classes_and_methods = array(
203 - array( 'Article' => 'doPurge' ),
204 - array( 'Linker' => 'link' ),
205 - array( 'OutputPage' => 'isPrintable' ),
206 - array( 'Parser' => 'getTitle' ),
207 - array( 'Parser' => 'setHook' ),
208 - array( 'Parser' => 'recursiveTagParse' ),
209 - array( 'ParserCache' => 'getKey' ),
210 - array( 'ParserCache' => 'singleton' ),
211 - array( 'Title' => 'getArticleID' ),
212 - array( 'Title' => 'getPrefixedText' ),
213 - array( 'Title' => 'makeTitle' ),
214 - array( 'Title' => 'makeTitleSafe' ),
215 - array( 'Title' => 'newFromID' )
216 - );
217 - foreach ( $required_classes_and_methods as &$check ) {
218 - list( $object, $method ) = each( $check );
219 - if ( !method_exists( $object, $method ) ) {
220 - die( "QPoll extension requires " . $object . "::" . $method . " method to be available.<br />\n" .
221 - "Your version of MediaWiki is incompatible with this extension.\n" );
222 - }
223 - }
224 - if ( !defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
225 - die( "QPoll extension requires ParserFirstCallInit hook.\nPlease upgrade your MediaWiki installation first.\n" );
226 - }
227 - }
228 -
229210 /**
230211 * Autoload classes from the map provided
231212 */
@@ -253,8 +234,7 @@
254235 global $wgGroupPermissions;
255236 global $wgDebugLogGroups;
256237
257 - # core check and local / remote path
258 - self::coreRequirements();
 238+ # local / remote path
259239 self::$ExtDir = str_replace( "\\", "/", dirname( __FILE__ ) );
260240 $dirs = explode( '/', self::$ExtDir );
261241 $top_dir = array_pop( $dirs );
@@ -272,6 +252,9 @@
273253 self::autoLoad( array(
274254 'qp_user.php' => array( 'qp_Setup', 'qp_Renderer', 'qp_FunctionsHook' ),
275255
 256+ ## DB schema updater
 257+ 'maintenance/qp_schemaupdater.php' => 'qp_SchemaUpdater',
 258+
276259 ## collection of the questions
277260 'qp_question_collection.php' => 'qp_QuestionCollection',
278261
@@ -309,8 +292,10 @@
310293 # (combined question storage & view)
311294 'qp_questiondata.php' => array( 'qp_QuestionData', 'qp_TextQuestionData' ),
312295
313 - # results page
314 - 'qp_results.php' => array( 'qp_SpecialPage', 'qp_QueryPage', 'PollResults' ),
 296+ # special pages
 297+ 'specials/qp_special.php' => array( 'qp_SpecialPage', 'qp_QueryPage' ),
 298+ 'specials/qp_results.php' => 'PollResults',
 299+ 'specials/qp_webinstall.php' => array( 'qp_WebInstall' ),
315300
316301 # interpretation of answers
317302 'qp_interpret.php' => 'qp_Interpret',
@@ -319,6 +304,7 @@
320305
321306 # TODO: Use the new technique for i18n of special page aliases
322307 $wgSpecialPages['PollResults'] = 'PollResults';
 308+ $wgSpecialPages['QPollWebInstall'] = 'qp_WebInstall';
323309 # TODO: Use the new technique for i18n of magic words
324310 # instantiating fake instance for PHP < 5.2.3, which does not support 'Class::method' type of callbacks
325311 $wgHooks['LanguageGetMagic'][] =
@@ -326,8 +312,8 @@
327313 $wgHooks['ParserFirstCallInit'][] =
328314 $wgHooks['LoadAllMessages'][] =
329315 $wgHooks['ParserAfterTidy'][] =
330 - $wgHooks['CanonicalNamespaces'][] =
331 - new qp_Setup;
 316+ $wgHooks['CanonicalNamespaces'][] = new qp_Setup;
 317+ $wgHooks['LoadExtensionSchemaUpdates'][] = new qp_SchemaUpdater;
332318
333319 if ( self::mediaWikiVersionCompare( '1.17' ) ) {
334320 # define namespaces for the interpretation scripts and their talk pages
@@ -442,7 +428,11 @@
443429 $parserCache = ParserCache::singleton();
444430 $key = $parserCache->getKey( self::$article, self::$user );
445431 $parserMemc->delete( $key );
446 - self::$article->doPurge();
 432+ if ( method_exists( 'Article', 'doPurge' ) ) {
 433+ self::$article->doPurge();
 434+ } else {
 435+ WikiPage::factory( self::$title )->doPurge();
 436+ }
447437 }
448438 }
449439
@@ -452,7 +442,11 @@
453443 global $qp_AnonForwardedFor; // deprecated since v0.6.5
454444 global $wgUser;
455445 self::$article = $article;
456 - # borrowed from Title::getUserPermissionsErrors() MW v1.16
 446+ self::$title = $title;
 447+ # in MW v1.15 / v1.16 user object was stub;
 448+ # in MW v1.19 it seems to be real object.
 449+ # Unstub for the versions where it is stubbed.
 450+ # Borrowed from Title::getUserPermissionsErrors() MW v1.16
457451 if ( !StubObject::isRealObject( $user ) ) {
458452 // Since StubObject is always used on globals, we can unstub $wgUser here and set $user = $wgUser
459453 global $wgUser;
Index: trunk/extensions/QPoll/tables/qpoll_0.7.0.src
@@ -6,7 +6,7 @@
77 `dependance` mediumtext NOT NULL,
88 PRIMARY KEY poll (pid),
99 UNIQUE INDEX article_poll (article_id,poll_id(128))
10 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 10+) /*$wgDBTableOptions*/;
1111
1212 CREATE TABLE /*$wgDBprefix*/qp_question_desc (
1313 `pid` int unsigned NOT NULL,
@@ -15,7 +15,7 @@
1616 `common_question` mediumtext NOT NULL,
1717 PRIMARY KEY question (pid,question_id),
1818 INDEX poll (pid)
19 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 19+) /*$wgDBTableOptions*/;
2020
2121 CREATE TABLE /*$wgDBprefix*/qp_question_categories (
2222 `pid` int unsigned NOT NULL,
@@ -24,7 +24,7 @@
2525 `cat_name` tinytext NOT NULL,
2626 PRIMARY KEY category (pid,question_id,cat_id),
2727 INDEX poll (pid)
28 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 28+) /*$wgDBTableOptions*/;
2929
3030 CREATE TABLE /*$wgDBprefix*/qp_question_proposals (
3131 `pid` int unsigned NOT NULL,
@@ -33,7 +33,7 @@
3434 `proposal_text` tinytext NOT NULL,
3535 PRIMARY KEY proposal (pid,question_id,proposal_id),
3636 INDEX poll (pid)
37 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 37+) /*$wgDBTableOptions*/;
3838
3939 CREATE TABLE /*$wgDBprefix*/qp_question_answers (
4040 `uid` int unsigned NOT NULL,
@@ -45,13 +45,13 @@
4646 PRIMARY KEY answer (uid,pid,question_id,proposal_id,cat_id),
4747 INDEX user_vote (uid,pid),
4848 INDEX poll_question (pid,question_id)
49 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 49+) /*$wgDBTableOptions*/;
5050
5151 CREATE TABLE /*$wgDBprefix*/qp_users_polls (
5252 `uid` int unsigned NOT NULL,
5353 `pid` int unsigned NOT NULL,
5454 PRIMARY KEY user_poll (uid,pid)
55 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 55+) /*$wgDBTableOptions*/;
5656
5757 CREATE TABLE /*$wgDBprefix*/qp_users (
5858 `uid` int unsigned NOT NULL auto_increment,
@@ -59,4 +59,4 @@
6060 PRIMARY KEY unique_name (uid,name(64)),
6161 INDEX user_id (uid),
6262 INDEX username (name(64))
63 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 63+) /*$wgDBTableOptions*/;
Index: trunk/extensions/QPoll/tables/qpoll_random_questions.src
@@ -5,4 +5,4 @@
66 PRIMARY KEY user_poll_question (uid,pid,question_id),
77 INDEX user_seed (uid,pid),
88 INDEX poll (pid)
9 -) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 9+) /*$wgDBTableOptions*/;
Index: trunk/extensions/QPoll/tables/qpoll_trunk.sql
@@ -1,6 +1,8 @@
2 -/* WARNING: do not use directly in case your wiki DB setup uses table prefixes
3 - * use Special:PollResults page instead
4 - * this file is primarily for debugging
 2+/**
 3+ * WARNING: This file will drop existing data in QPoll tables, if there's any!
 4+ * Do not use directly in case your wiki DB setup uses table prefixes.
 5+ * Use Special:PollResults page instead.
 6+ * This file is primarily for debugging.
57 */
68
79 DROP TABLE IF EXISTS `qp_poll_desc`;
@@ -43,7 +45,7 @@
4446 `pid` int unsigned NOT NULL,
4547 `question_id` int unsigned NOT NULL,
4648 `proposal_id` int unsigned NOT NULL,
47 - `proposal_text` tinytext NOT NULL,
 49+ `proposal_text` text NOT NULL,
4850 PRIMARY KEY proposal (pid,question_id,proposal_id),
4951 INDEX poll (pid)
5052 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Status & tagging log