Index: trunk/extensions/QPoll/qp_results.php |
— | — | @@ -37,8 +37,25 @@ |
38 | 38 | die( "This file is part of the QPoll extension. It is not a valid entry point.\n" ); |
39 | 39 | } |
40 | 40 | |
41 | | -class PollResults extends SpecialPage { |
| 41 | +class qp_SpecialPage extends SpecialPage { |
42 | 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 | + |
43 | 60 | public function __construct() { |
44 | 61 | parent::__construct( 'PollResults', 'read' ); |
45 | 62 | # for MW 1.15 (still being used by many customers) |
— | — | @@ -50,7 +67,6 @@ |
51 | 68 | |
52 | 69 | static $accessPermissions = array( 'read', 'pollresults' ); |
53 | 70 | |
54 | | - static $skin = null; |
55 | 71 | static $UsersLink = ""; |
56 | 72 | static $PollsLink = ""; |
57 | 73 | |
— | — | @@ -92,14 +108,11 @@ |
93 | 109 | # MW < 1.17 |
94 | 110 | $wgOut->addExtensionStyle( qp_Setup::$ScriptPath . '/clientside/qp_results.css' ); |
95 | 111 | } |
96 | | - if ( self::$skin == null ) { |
97 | | - self::$skin = $wgUser->getSkin(); |
98 | | - } |
99 | 112 | if ( self::$UsersLink == "" ) { |
100 | | - self::$UsersLink = self::$skin->link( $this->getTitle(), wfMsg( 'qp_users_list' ), array( "style"=>"font-weight:bold;" ), array( 'action'=>'users' ) ); |
| 113 | + self::$UsersLink = $this->qpLink( $this->getTitle(), wfMsg( 'qp_users_list' ), array( "style"=>"font-weight:bold;" ), array( 'action'=>'users' ) ); |
101 | 114 | } |
102 | 115 | if ( self::$PollsLink == "" ) { |
103 | | - self::$PollsLink = self::$skin->link( $this->getTitle(), wfMsg( 'qp_polls_list' ), array( "style"=>"font-weight:bold;" ) ); |
| 116 | + self::$PollsLink = $this->qpLink( $this->getTitle(), wfMsg( 'qp_polls_list' ), array( "style"=>"font-weight:bold;" ) ); |
104 | 117 | } |
105 | 118 | $wgOut->addHTML( '<div class="qpoll">' ); |
106 | 119 | $output = ""; |
— | — | @@ -192,6 +205,20 @@ |
193 | 206 | } |
194 | 207 | |
195 | 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 | + /** |
196 | 223 | * check for the existence of multiple fields in the selected database table |
197 | 224 | * @param $table table name |
198 | 225 | * @param $fields field names |
— | — | @@ -216,43 +243,50 @@ |
217 | 244 | private function checkTables() { |
218 | 245 | $db = & wfGetDB( DB_SLAVE ); |
219 | 246 | $sql_tables = array( |
220 | | - "qp_poll_desc", |
221 | | - "qp_question_desc", |
222 | | - "qp_question_categories", |
223 | | - "qp_question_proposals", |
224 | | - "qp_question_answers", |
225 | | - "qp_users_polls", |
226 | | - "qp_users"); |
| 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 | + ); |
227 | 260 | $addFields = array( |
228 | 261 | 'qpoll_interpretation.src' => array( |
229 | 262 | 'qp_poll_desc' => array( 'interpretation_namespace', 'interpretation_title' ), |
230 | 263 | 'qp_users_polls' => array( 'attempts', 'short_interpretation', 'long_interpretation' ) |
231 | 264 | ) |
232 | 265 | ); |
233 | | - // check whether the tables were initialized |
234 | | - $tablesFound = 0; |
| 266 | + /* check whether the tables were initialized */ |
235 | 267 | $result = true; |
236 | | - foreach ( $sql_tables as $table ) { |
237 | | - if ( $db->tableExists( $table ) ) { |
238 | | - $tablesFound++; |
| 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."; |
239 | 280 | } |
240 | 281 | } |
241 | | - if ( $tablesFound != count( $sql_tables ) ) { |
242 | | - # some tables are missing, serious DB error |
243 | | - return "Some of the extension database tables are missing.<br />Please restore from backup or drop the remaining extension tables, then reload this page."; |
244 | | - } |
245 | | - if ( $tablesFound == 0 ) { |
246 | | - # no tables were found, initialize the DB completely with minimal version |
247 | | - if ( ( $r = $db->sourceFile( qp_Setup::$ExtDir . '/tables/qpoll_0.7.0.src' ) ) !== true ) { |
248 | | - return $r; |
249 | | - } |
250 | | - } |
251 | 282 | /* start of SQL updates */ |
252 | | - $scriptsToRun = array(); |
| 283 | + $scriptsToRun = $tablesUpgrade = array(); |
253 | 284 | foreach( $addFields as $script => &$table_list ) { |
254 | 285 | foreach( $table_list as $table => &$fields_list ) { |
255 | 286 | if ( !$this->fieldsExists( $table, $fields_list ) ) { |
256 | 287 | $scriptsToRun[$script] = true; |
| 288 | + if ( array_search( $table, $tablesUpgrade ) === false ) { |
| 289 | + array_push( $tablesUpgrade, $table ); |
| 290 | + } |
257 | 291 | } |
258 | 292 | } |
259 | 293 | } |
— | — | @@ -262,14 +296,17 @@ |
263 | 297 | } |
264 | 298 | } |
265 | 299 | /* end of SQL updates */ |
266 | | - if ( $tablesFound == 0 ) { |
267 | | - $result = 'Tables were initialized.'; |
| 300 | + if ( count( $tablesInit ) > 0 ) { |
| 301 | + $result = 'The following table(s) were initialized: ' . implode( ', ', $tablesInit ) . '<br />'; |
268 | 302 | } |
269 | 303 | if ( count( $scriptsToRun ) > 0 ) { |
270 | | - $result = 'Tables were upgraded.'; |
| 304 | + if ( !is_string( $result ) ) { |
| 305 | + $result = ''; |
| 306 | + } |
| 307 | + $result = 'The following table(s) were upgraded:' . implode( ', ', $tablesUpgrade ) . '<br />'; |
271 | 308 | } |
272 | 309 | if ( is_string( $result ) ) { |
273 | | - $result .= '<br />Please <a href="#" onclick="window.location.reload()">reload</a> this page to view future page edits.'; |
| 310 | + $result .= 'Please <a href="#" onclick="window.location.reload()">reload</a> this page to view future page edits.'; |
274 | 311 | } |
275 | 312 | return $result; |
276 | 313 | } |
— | — | @@ -296,29 +333,35 @@ |
297 | 334 | } |
298 | 335 | |
299 | 336 | private function showUserVote( $pid, $uid ) { |
300 | | - $output = ""; |
301 | | - if ( $pid !== null && $uid !== null ) { |
302 | | - $pollStore = new qp_PollStore( array( 'from'=>'pid', 'pid'=> $pid ) ); |
303 | | - if ( $pollStore->pid !== null ) { |
304 | | - $pollStore->loadQuestions(); |
305 | | - $userName = $pollStore->getUserName( $uid ); |
306 | | - if ( $userName !== false ) { |
307 | | - $userTitle = Title::makeTitleSafe( NS_USER, $userName ); |
308 | | - $user_link = self::$skin->link( $userTitle, $userName ); |
309 | | - $pollStore->setLastUser( $userName, false ); |
310 | | - if ( $pollStore->loadUserVote() ) { |
311 | | - $poll_title = $pollStore->getTitle(); |
312 | | - # 'parentheses' is unavailable in MediaWiki 1.15.x |
313 | | - $poll_link = self::$skin->link( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) ); |
314 | | - $output .= wfMsg( 'qp_browse_to_user', $user_link ) . "<br />\n"; |
315 | | - $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n"; |
316 | | - $output .= $this->showAnswerHeader( $pollStore ); |
317 | | - foreach ( $pollStore->Questions as $qkey => &$qdata ) { |
318 | | - $output .= "<br />\n<b>" . $qkey . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n"; |
319 | | - $output .= $this->displayUserQuestionVote( $qdata ); |
320 | | - } |
321 | | - } |
322 | | - } |
| 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 | + $pollStore->loadRandomQuestions( $userName ); |
| 350 | + $userTitle = Title::makeTitleSafe( NS_USER, $userName ); |
| 351 | + $user_link = $this->qpLink( $userTitle, $userName ); |
| 352 | + $pollStore->setLastUser( $userName, false ); |
| 353 | + if ( !$pollStore->loadUserVote() ) { |
| 354 | + return ''; |
| 355 | + } |
| 356 | + $poll_title = $pollStore->getTitle(); |
| 357 | + # 'parentheses' key is unavailable in MediaWiki 1.15.x |
| 358 | + $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) ); |
| 359 | + $output = wfMsg( 'qp_browse_to_user', $user_link ) . "<br />\n"; |
| 360 | + $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n"; |
| 361 | + $output .= $this->showAnswerHeader( $pollStore ); |
| 362 | + foreach ( $pollStore->Questions as &$qdata ) { |
| 363 | + if ( $pollStore->isUsedQuestion( $qdata->question_id ) ) { |
| 364 | + $output .= "<br />\n<b>" . $qdata->question_id . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n"; |
| 365 | + $output .= $this->displayUserQuestionVote( $qdata ); |
323 | 366 | } |
324 | 367 | } |
325 | 368 | return $output; |
— | — | @@ -380,10 +423,10 @@ |
381 | 424 | $pollStore->calculateStatistics(); |
382 | 425 | $poll_title = $pollStore->getTitle(); |
383 | 426 | # 'parentheses' is unavailable in 1.14.x |
384 | | - $poll_link = self::$skin->link( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) ); |
| 427 | + $poll_link = $this->qpLink( $poll_title, $poll_title->getPrefixedText() . wfMsg( 'word-separator' ) . wfMsg( 'qp_parentheses', $pollStore->mPollId ) ); |
385 | 428 | $output .= wfMsg( 'qp_browse_to_poll', $poll_link ) . "<br />\n"; |
386 | | - $output .= self::$skin->link( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style"=>"font-weight:bold;" ), array( 'action'=>'stats_xls', 'id'=>$pid ) ) . "<br />\n"; |
387 | | - $output .= self::$skin->link( $this->getTitle(), wfMsg( 'qp_voices_to_xls' ), array( "style"=>"font-weight:bold;" ), array( 'action'=>'voices_xls', 'id'=>$pid ) ) . "<br />\n"; |
| 429 | + $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_export_to_xls' ), array( "style"=>"font-weight:bold;" ), array( 'action'=>'stats_xls', 'id'=>$pid ) ) . "<br />\n"; |
| 430 | + $output .= $this->qpLink( $this->getTitle(), wfMsg( 'qp_voices_to_xls' ), array( "style"=>"font-weight:bold;" ), array( 'action'=>'voices_xls', 'id'=>$pid ) ) . "<br />\n"; |
388 | 431 | foreach ( $pollStore->Questions as $qkey => &$qdata ) { |
389 | 432 | $output .= "<br />\n<b>" . $qkey . ".</b> " . qp_Setup::entities( $qdata->CommonQuestion ) . "<br />\n"; |
390 | 433 | $output .= $this->displayQuestionStats( $pid, $qdata ); |
— | — | @@ -626,7 +669,7 @@ |
627 | 670 | if ( $cell == 0.0 && $qdata->question_id !==null ) { |
628 | 671 | $cell = array( 0=> $formatted_cell, "style"=>"color:gray" ); |
629 | 672 | } else { |
630 | | - $cell = array( 0=>self::$skin->link( $current_title, $formatted_cell, |
| 673 | + $cell = array( 0=>$this->qpLink( $current_title, $formatted_cell, |
631 | 674 | array( "title"=>wfMsgExt( 'qp_votes_count', array( 'parsemag' ), $qdata->Votes[ $propkey ][ $catkey ] ) ), |
632 | 675 | array( "action"=>"qpcusers", "id"=>$pid, "qid"=>$qdata->question_id, "pid"=>$propkey, "cid"=>$catkey ) ) ); |
633 | 676 | } |
— | — | @@ -666,27 +709,20 @@ |
667 | 710 | * We do not extend QueryPage anymore because it is purposely made incompatible in 1.18+ |
668 | 711 | * thus, it is much safer to implement a larger subset of pager itself |
669 | 712 | */ |
670 | | -abstract class qp_QueryPage extends SpecialPage { |
| 713 | +abstract class qp_QueryPage extends qp_SpecialPage { |
671 | 714 | |
672 | | - static $skin = null; |
673 | 715 | var $listoutput = false; |
674 | 716 | |
675 | 717 | public function __construct() { |
676 | | - global $wgUser; |
677 | | - if ( self::$skin == null ) { |
678 | | - self::$skin = $wgUser->getSkin(); |
679 | | - } |
680 | 718 | parent::__construct( $this->queryPageName() ); |
681 | 719 | } |
682 | 720 | |
683 | 721 | function doQuery( $offset, $limit, $shownavigation=true ) { |
684 | | - global $wgUser, $wgOut, $wgLang, $wgContLang; |
| 722 | + global $wgOut, $wgContLang; |
685 | 723 | |
686 | 724 | $res = $this->getIntervalResults( $offset, $limit ); |
687 | 725 | $num = count( $res ); |
688 | 726 | |
689 | | - $sk = $wgUser->getSkin(); |
690 | | - |
691 | 727 | if($shownavigation) { |
692 | 728 | $wgOut->addHTML( $this->getPageHeader() ); |
693 | 729 | |
— | — | @@ -713,7 +749,7 @@ |
714 | 750 | $s[] = $this->openList( $offset ); |
715 | 751 | |
716 | 752 | foreach ($res as $r) { |
717 | | - $format = $this->formatResult( $sk, $r ); |
| 753 | + $format = $this->formatResult( $r ); |
718 | 754 | if ( $format ) { |
719 | 755 | $s[] = $this->listoutput ? $format : "<li>{$format}</li>\n"; |
720 | 756 | } |
— | — | @@ -783,10 +819,10 @@ |
784 | 820 | $this->cmd = $cmd; |
785 | 821 | if ( $cmd == 'users' ) { |
786 | 822 | $this->order_by = 'count(pid) DESC, name ASC '; |
787 | | - $this->different_order_by_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_order_by_username' ), array(), array( "action"=>"users_a" ) ); |
| 823 | + $this->different_order_by_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_order_by_username' ), array(), array( "action"=>"users_a" ) ); |
788 | 824 | } else { |
789 | 825 | $this->order_by = 'name '; |
790 | | - $this->different_order_by_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_order_by_polls_count' ), array(), array( "action"=>"users" ) ); |
| 826 | + $this->different_order_by_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_order_by_polls_count' ), array(), array( "action"=>"users" ) ); |
791 | 827 | } |
792 | 828 | } |
793 | 829 | |
— | — | @@ -809,16 +845,16 @@ |
810 | 846 | return $result; |
811 | 847 | } |
812 | 848 | |
813 | | - function formatResult( $skin, $result ) { |
| 849 | + function formatResult( $result ) { |
814 | 850 | global $wgLang, $wgContLang; |
815 | 851 | $link = ""; |
816 | 852 | if ( $result !== null ) { |
817 | 853 | $uid = intval( $result->uid ); |
818 | 854 | $userName = $result->username; |
819 | 855 | $userTitle = Title::makeTitleSafe( NS_USER, $userName ); |
820 | | - $user_link = self::$skin->link( $userTitle, $userName ); |
821 | | - $user_polls_link = self::$skin->link( $this->getTitle(), wfMsgExt( 'qp_user_polls_link', array( 'parsemag' ), $result->pidcount, $userName ) , array(), array( "uid"=>$uid, "action"=>"upolls" ) ); |
822 | | - $user_missing_polls_link = self::$skin->link( $this->getTitle(), wfMsgExt( 'qp_user_missing_polls_link', 'parsemag', $userName ) , array(), array( "uid"=>$uid, "action"=>"nupolls" ) ); |
| 856 | + $user_link = $this->qpLink( $userTitle, $userName ); |
| 857 | + $user_polls_link = $this->qpLink( $this->getTitle(), wfMsgExt( 'qp_user_polls_link', array( 'parsemag' ), $result->pidcount, $userName ) , array(), array( "uid"=>$uid, "action"=>"upolls" ) ); |
| 858 | + $user_missing_polls_link = $this->qpLink( $this->getTitle(), wfMsgExt( 'qp_user_missing_polls_link', 'parsemag', $userName ) , array(), array( "uid"=>$uid, "action"=>"nupolls" ) ); |
823 | 859 | $link = $user_link . ': ' . $user_polls_link . ', ' . $user_missing_polls_link; |
824 | 860 | } |
825 | 861 | return $link; |
— | — | @@ -866,7 +902,7 @@ |
867 | 903 | } |
868 | 904 | if ( $userName !== false ) { |
869 | 905 | $userTitle = Title::makeTitleSafe( NS_USER, $userName ); |
870 | | - $user_link = self::$skin->link( $userTitle, $userName ); |
| 906 | + $user_link = $this->qpLink( $userTitle, $userName ); |
871 | 907 | 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>'; |
872 | 908 | } |
873 | 909 | } |
— | — | @@ -892,13 +928,13 @@ |
893 | 929 | return $result; |
894 | 930 | } |
895 | 931 | |
896 | | - function formatResult( $skin, $result ) { |
| 932 | + function formatResult( $result ) { |
897 | 933 | global $wgLang, $wgContLang; |
898 | 934 | $poll_title = Title::makeTitle( $result->ns, $result->title, qp_AbstractPoll::s_getPollTitleFragment( $result->poll_id, '' ) ); |
899 | 935 | $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) ); |
900 | 936 | $pollname = qp_Setup::specialchars( $result->poll_id ); |
901 | | - $goto_link = self::$skin->link( $poll_title, wfMsg( 'qp_source_link' ) ); |
902 | | - $voice_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_voice_link' . ($this->inverse ? "_inv" : "") ), array(), array( "id"=>intval( $result->pid), "uid"=>$this->uid, "action"=>"uvote" ) ); |
| 937 | + $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) ); |
| 938 | + $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ($this->inverse ? "_inv" : "") ), array(), array( "id"=>intval( $result->pid), "uid"=>$this->uid, "action"=>"uvote" ) ); |
903 | 939 | $link = wfMsg( 'qp_results_line_qupl', $pagename, $pollname, $voice_link ); |
904 | 940 | return $link; |
905 | 941 | } |
— | — | @@ -933,15 +969,15 @@ |
934 | 970 | return $result; |
935 | 971 | } |
936 | 972 | |
937 | | - function formatResult( $skin, $result ) { |
| 973 | + function formatResult( $result ) { |
938 | 974 | global $wgLang, $wgContLang; |
939 | 975 | $poll_title = Title::makeTitle( $result->ns, $result->title, qp_AbstractPoll::getPollTitleFragment( $result->poll_id, '' ) ); |
940 | 976 | $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) ); |
941 | 977 | $pollname = qp_Setup::specialchars( $result->poll_id ); |
942 | | - $goto_link = self::$skin->link( $poll_title, wfMsg( 'qp_source_link' ) ); |
943 | | - $voices_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_stats_link' ), array(), array( "id"=>intval( $result->pid), "action"=>"stats" ) ); |
944 | | - $users_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_users_link' ), array(), array( "id"=>intval( $result->pid), "action"=>"pulist" ) ); |
945 | | - $not_participated_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_not_participated_link' ), array(), array( "id"=>intval( $result->pid), "action"=>"npulist" ) ); |
| 978 | + $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) ); |
| 979 | + $voices_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_stats_link' ), array(), array( "id"=>intval( $result->pid), "action"=>"stats" ) ); |
| 980 | + $users_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_users_link' ), array(), array( "id"=>intval( $result->pid), "action"=>"pulist" ) ); |
| 981 | + $not_participated_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_not_participated_link' ), array(), array( "id"=>intval( $result->pid), "action"=>"npulist" ) ); |
946 | 982 | $link = wfMsg( 'qp_results_line_qpl', $pagename, $pollname, $goto_link, $voices_link, $users_link, $not_participated_link ); |
947 | 983 | return $link; |
948 | 984 | } |
— | — | @@ -979,7 +1015,7 @@ |
980 | 1016 | $poll_title = Title::makeTitle( intval( $row->ns ), $row->title, qp_AbstractPoll::getPollTitleFragment( $row->poll_id, '' ) ); |
981 | 1017 | $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) ); |
982 | 1018 | $pollname = qp_Setup::specialchars( $row->poll_id ); |
983 | | - $goto_link = self::$skin->link( $poll_title, wfMsg( 'qp_source_link' ) ); |
| 1019 | + $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) ); |
984 | 1020 | $spec = wfMsg( 'qp_header_line_qpul', wfMsg( $this->inverse ? 'qp_not_participated_link' : 'qp_users_link'), $pagename, $pollname ); |
985 | 1021 | $head[] = PollResults::getPollsLink(); |
986 | 1022 | $head[] = PollResults::getUsersLink(); |
— | — | @@ -1008,15 +1044,15 @@ |
1009 | 1045 | return $result; |
1010 | 1046 | } |
1011 | 1047 | |
1012 | | - function formatResult( $skin, $result ) { |
| 1048 | + function formatResult( $result ) { |
1013 | 1049 | global $wgLang, $wgContLang; |
1014 | 1050 | $link = ""; |
1015 | 1051 | if ( $result !== null ) { |
1016 | 1052 | $uid = intval( $result->uid ); |
1017 | 1053 | $userName = $result->username; |
1018 | 1054 | $userTitle = Title::makeTitleSafe( NS_USER, $userName ); |
1019 | | - $user_link = self::$skin->link( $userTitle, $userName ); |
1020 | | - $voice_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_voice_link' . ($this->inverse ? "_inv" : "") ), array(), array( "id"=>intval( $this->pid), "uid"=>$uid, "action"=>"uvote" ) ); |
| 1055 | + $user_link = $this->qpLink( $userTitle, $userName ); |
| 1056 | + $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ($this->inverse ? "_inv" : "") ), array(), array( "id"=>intval( $this->pid), "uid"=>$uid, "action"=>"uvote" ) ); |
1021 | 1057 | $link = wfMsg( 'qp_results_line_qpul', $user_link, $voice_link ); |
1022 | 1058 | } |
1023 | 1059 | return $link; |
— | — | @@ -1072,7 +1108,7 @@ |
1073 | 1109 | $poll_title = Title::makeTitle( intval( $this->ns ), $this->title, qp_AbstractPoll::getPollTitleFragment( $this->poll_id, '' ) ); |
1074 | 1110 | $pagename = qp_Setup::specialchars( $wgContLang->convert( $poll_title->getPrefixedText() ) ); |
1075 | 1111 | $pollname = qp_Setup::specialchars( $this->poll_id ); |
1076 | | - $goto_link = self::$skin->link( $poll_title, wfMsg( 'qp_source_link' ) ); |
| 1112 | + $goto_link = $this->qpLink( $poll_title, wfMsg( 'qp_source_link' ) ); |
1077 | 1113 | $spec = wfMsg( 'qp_header_line_qpul', wfMsg( 'qp_users_link' ), $pagename, $pollname ); |
1078 | 1114 | $head[] = PollResults::getPollsLink(); |
1079 | 1115 | $head[] = PollResults::getUsersLink(); |
— | — | @@ -1126,15 +1162,15 @@ |
1127 | 1163 | return $result; |
1128 | 1164 | } |
1129 | 1165 | |
1130 | | - function formatResult( $skin, $result ) { |
| 1166 | + function formatResult( $result ) { |
1131 | 1167 | global $wgLang, $wgContLang; |
1132 | 1168 | $link = ""; |
1133 | 1169 | if ( $result !== null ) { |
1134 | 1170 | $uid = intval( $result->uid ); |
1135 | 1171 | $userName = $result->username; |
1136 | 1172 | $userTitle = Title::makeTitleSafe( NS_USER, $userName ); |
1137 | | - $user_link = self::$skin->link( $userTitle, $userName ); |
1138 | | - $voice_link = self::$skin->link( $this->getTitle(), wfMsg( 'qp_voice_link' . ($this->inverse ? "_inv" : "" ) ), array(), array( "id"=>intval( $this->pid), "uid"=>$uid, "action"=>"uvote" ) ); |
| 1173 | + $user_link = $this->qpLink( $userTitle, $userName ); |
| 1174 | + $voice_link = $this->qpLink( $this->getTitle(), wfMsg( 'qp_voice_link' . ($this->inverse ? "_inv" : "" ) ), array(), array( "id"=>intval( $this->pid), "uid"=>$uid, "action"=>"uvote" ) ); |
1139 | 1175 | $text_answer = ($result->text_answer == '') ? '' : '<i>' . qp_Setup::entities( $result->text_answer ) . '</i>'; |
1140 | 1176 | $link = wfMsg( 'qp_results_line_qucl', $user_link, $voice_link, $text_answer ); |
1141 | 1177 | } |
Index: trunk/extensions/QPoll/ctrl/qp_abstractpoll.php |
— | — | @@ -52,8 +52,9 @@ |
53 | 53 | static $sOrderId = 0; // order of polls on the page (used for sorting of the output) |
54 | 54 | static $sPrevPollIDs = array(); // used to check uniqueness of PollId on the page |
55 | 55 | |
56 | | - # array of question objects associated with current poll |
57 | | - var $questions = array(); |
| 56 | + # collection of question objects associated with current poll |
| 57 | + var $questions; |
| 58 | + |
58 | 59 | # current user name |
59 | 60 | var $username; |
60 | 61 | |
Index: trunk/extensions/QPoll/ctrl/qp_abstractquestion.php |
— | — | @@ -6,6 +6,15 @@ |
7 | 7 | |
8 | 8 | abstract class qp_AbstractQuestion { |
9 | 9 | |
| 10 | + # indicates whether current question is used or not; |
| 11 | + # also provides sparce enumeration of questions (unused questions are not counted) |
| 12 | + # 1..n when the question is active; |
| 13 | + # false when the question is hidden (used by randomizer) |
| 14 | + var $usedId = false; |
| 15 | + # sequental number of question (starting from 1); matches to usedId |
| 16 | + # when the collection of the questions is not sparce (was not randomized) |
| 17 | + var $mQuestionId; |
| 18 | + |
10 | 19 | var $mState = ''; // current state of question parsing (no error) |
11 | 20 | # default type and subtype of the question; should always be properly initialized in derived $this->parseMainHeader(); |
12 | 21 | var $mType = 'unknown'; |
— | — | @@ -34,7 +43,8 @@ |
35 | 44 | function __construct( qp_AbstractPoll $poll, qp_AbstractView $view, $questionId ) { |
36 | 45 | global $wgRequest; |
37 | 46 | $this->mRequest = &$wgRequest; |
38 | | - $this->mQuestionId = $questionId; |
| 47 | + # the question collection is not sparce by default |
| 48 | + $this->mQuestionId = $this->usedId = $questionId; |
39 | 49 | $this->mProposalPattern = '`^[^\|\!].*`u'; |
40 | 50 | $this->mCategoryPattern = '`^\|(\n|[^\|].*\n)`u'; |
41 | 51 | $view->setController( $this ); |
Index: trunk/extensions/QPoll/ctrl/qp_pollstats.php |
— | — | @@ -78,7 +78,7 @@ |
79 | 79 | * @return boolean true - stop further processing, false - continue processing |
80 | 80 | */ |
81 | 81 | function parseInput( $input ) { |
82 | | - $this->questions = array(); |
| 82 | + $this->questions = new qp_QuestionCollection(); |
83 | 83 | # question attributes split pattern |
84 | 84 | $splitPattern = '`\s*{|}\s*\n*`u'; |
85 | 85 | # preg_split counts the matches starting from zero |
— | — | @@ -103,19 +103,20 @@ |
104 | 104 | # there cannot be type attribute of question in statistical display mode |
105 | 105 | $question->setState( 'error', wfMsg( 'qp_error_type_in_stats_mode', $type ) ); |
106 | 106 | } |
107 | | - $this->questions[] = $question; |
| 107 | + $this->questions->add( $question ); |
108 | 108 | } |
109 | 109 | # analyze question headers |
110 | 110 | # check for showresults attribute |
111 | 111 | $questions_set = array(); |
112 | | - foreach ( $this->questions as &$question ) { |
| 112 | + $this->questions->reset(); |
| 113 | + while ( is_object( $question = $this->questions->iterate() ) ) { |
113 | 114 | if ( $question->view->hasShowResults() ) { |
114 | 115 | $questions_set[] = $question->mQuestionId; |
115 | 116 | } |
116 | 117 | } |
117 | 118 | # load the statistics for all/selective/none of questions |
118 | 119 | if ( count( $questions_set ) > 0 ) { |
119 | | - if ( count( $questions_set ) == count( $this->questions ) ) { |
| 120 | + if ( count( $questions_set ) == $this->questions->totalCount() ) { |
120 | 121 | $this->pollStore->loadTotals(); |
121 | 122 | } else { |
122 | 123 | $this->pollStore->loadTotals( $questions_set ); |
— | — | @@ -123,7 +124,8 @@ |
124 | 125 | $this->pollStore->calculateStatistics(); |
125 | 126 | } |
126 | 127 | # second pass: generate views |
127 | | - foreach ( $this->questions as &$question ) { |
| 128 | + $this->questions->reset(); |
| 129 | + while ( is_object( $question = $this->questions->iterate() ) ) { |
128 | 130 | $this->parseStats( $question ); |
129 | 131 | } |
130 | 132 | return false; |
Index: trunk/extensions/QPoll/ctrl/qp_poll.php |
— | — | @@ -45,7 +45,12 @@ |
46 | 46 | # optional address of the poll which must be answered first |
47 | 47 | var $dependsOn = ''; |
48 | 48 | # optional template used to interpret user vote in the Special:Pollresults page |
49 | | - var $interpretation = ''; |
| 49 | + var $interpretation = ''; |
| 50 | + # whether the questions of the poll has to be randomized |
| 51 | + # 0: questions are not randomized |
| 52 | + # 1..n: pull 1..n question from total number of defined questions; |
| 53 | + # separately stored for every (poll,user) in the poll store |
| 54 | + var $randomQuestionCount = 0; |
50 | 55 | # maximal count of attepts of answer submission ( < 1 for infinite ) |
51 | 56 | var $maxAttempts = 0; |
52 | 57 | |
— | — | @@ -59,6 +64,17 @@ |
60 | 65 | if ( array_key_exists('interpretation', $argv) ) { |
61 | 66 | $this->interpretation = trim( $argv['interpretation'] ); |
62 | 67 | } |
| 68 | + # randomize attr |
| 69 | + if ( array_key_exists('randomize', $argv) ) { |
| 70 | + if ( $argv['randomize'] === 'randomize' ) { |
| 71 | + $this->randomQuestionCount = 1; |
| 72 | + } else { |
| 73 | + $this->randomQuestionCount = intval( trim( $argv['randomize'] ) ); |
| 74 | + if ( $this->randomQuestionCount < 0 ) { |
| 75 | + $this->randomQuestionCount = 0; |
| 76 | + } |
| 77 | + } |
| 78 | + } |
63 | 79 | # max_attempts attr |
64 | 80 | $this->maxAttempts = qp_Setup::$max_submit_attempts; |
65 | 81 | if ( array_key_exists('max_attempts', $argv) ) { |
— | — | @@ -71,7 +87,7 @@ |
72 | 88 | $this->maxAttempts = qp_Setup::$max_submit_attempts; |
73 | 89 | } |
74 | 90 | } |
75 | | - # negative values are possible however meaningless (0 is infinite, >0 is finite) |
| 91 | + # negative values are possible however meaningless (<=0 is infinite, >0 is finite) |
76 | 92 | if ( $this->maxAttempts < 0 ) { |
77 | 93 | $this->maxAttempts = 0; |
78 | 94 | } |
— | — | @@ -156,6 +172,35 @@ |
157 | 173 | return true; |
158 | 174 | } |
159 | 175 | |
| 176 | + function setUsedQuestions() { |
| 177 | + # load random questions from DB (when available) |
| 178 | + $this->pollStore->loadRandomQuestions( $this->username ); |
| 179 | + if ( $this->randomQuestionCount > 0 ) { |
| 180 | + if ( $this->randomQuestionCount > $this->questions->totalCount() ) { |
| 181 | + $this->randomQuestionCount = $this->questions->totalCount(); |
| 182 | + } |
| 183 | + if ( is_array( $this->pollStore->randomQuestions ) ) { |
| 184 | + if ( count( $this->pollStore->randomQuestions ) == $this->randomQuestionCount ) { |
| 185 | + # count of random questions was not changed, no need to regenerate seed |
| 186 | + $this->questions->setUsedQuestions( $this->pollStore->randomQuestions ); |
| 187 | + return; |
| 188 | + } |
| 189 | + } |
| 190 | + # generate or regenerate random questions |
| 191 | + $this->questions->randomize( $this->randomQuestionCount ); |
| 192 | + $this->pollStore->randomQuestions = $this->questions->getUsedQuestions(); |
| 193 | + } else { |
| 194 | + if ( !is_array( $this->pollStore->randomQuestions ) ) { |
| 195 | + # random questions are disabled and no previous seed in DB |
| 196 | + return; |
| 197 | + } |
| 198 | + # there was stored random seed, will remove it at the end of this function |
| 199 | + $this->pollStore->randomQuestions = false; |
| 200 | + } |
| 201 | + # store random questions into DB |
| 202 | + $this->pollStore->setRandomQuestions(); |
| 203 | + } |
| 204 | + |
160 | 205 | /** |
161 | 206 | * Parses the text enclosed in poll tag |
162 | 207 | * Votes, when user have submitted data successfully |
— | — | @@ -164,8 +209,10 @@ |
165 | 210 | */ |
166 | 211 | function parseInput( $input ) { |
167 | 212 | global $wgTitle; |
168 | | - # parse the input; generates $this->questions[] array |
169 | | - $this->parseQuestions( $input ); |
| 213 | + # parse the input; generates $this->questions collection |
| 214 | + $this->parseQuestionsHeaders( $input ); |
| 215 | + $this->setUsedQuestions(); |
| 216 | + $this->parseQuestionsBodies(); |
170 | 217 | # check whether the poll was successfully submitted |
171 | 218 | if ( $this->attemptsLeft() === false ) { |
172 | 219 | # user has no attempts left, refuse to submit and |
— | — | @@ -264,12 +311,11 @@ |
265 | 312 | } |
266 | 313 | |
267 | 314 | /** |
268 | | - * Creates the set of poll questions in $this->questions[] |
269 | | - * Also calculates statistics for pollstore |
| 315 | + * Creates the collection of poll questions in $this->questions |
270 | 316 | * @param $input string poll in QPoll syntax |
271 | 317 | */ |
272 | | - function parseQuestions( $input ) { |
273 | | - $this->questions = array(); |
| 318 | + function parseQuestionsHeaders( $input ) { |
| 319 | + $this->questions = new qp_QuestionCollection(); |
274 | 320 | $splitPattern = '`(^|\n\s*)\n\s*{`u'; |
275 | 321 | $unparsedQuestions = preg_split( $splitPattern, $input, -1, PREG_SPLIT_NO_EMPTY ); |
276 | 322 | $questionPattern = '`(.*?[^|\}])\}[ \t]*(\n(.*)|$)`su'; |
— | — | @@ -285,23 +331,30 @@ |
286 | 332 | $header = isset( $matches[1] ) ? $matches[1] : ''; |
287 | 333 | $body = isset( $matches[3] ) ? $matches[3] : null; |
288 | 334 | $question = $this->parseQuestionHeader( $header, $body ); |
289 | | - $this->parseQuestionBody( $question ); |
290 | | - $this->questions[] = $question; |
| 335 | + $this->questions->add( $question ); |
291 | 336 | } else { |
292 | 337 | $buffer = $unparsedQuestion; |
293 | 338 | } |
294 | 339 | } |
295 | | - # analyze question headers |
| 340 | + } |
| 341 | + |
| 342 | + /** |
| 343 | + * Parses question bodies for every poll in collection |
| 344 | + * Also loads statistics from pollstore |
| 345 | + */ |
| 346 | + function parseQuestionsBodies() { |
296 | 347 | # check for showresults attribute |
297 | 348 | $questions_set = array(); |
298 | | - foreach( $this->questions as &$question ) { |
| 349 | + $this->questions->reset(); |
| 350 | + while ( is_object( $question = $this->questions->iterate() ) ) { |
| 351 | + $this->parseQuestionBody( $question ); |
299 | 352 | if ( $question->view->hasShowResults() ) { |
300 | 353 | $questions_set[] = $question->mQuestionId; |
301 | 354 | } |
302 | 355 | } |
303 | 356 | # load the statistics for all/selective/none of questions |
304 | 357 | if ( count( $questions_set ) > 0 ) { |
305 | | - if ( count( $questions_set ) == count( $this->questions ) ) { |
| 358 | + if ( count( $questions_set ) == $this->questions->totalCount() ) { |
306 | 359 | $this->pollStore->loadTotals(); |
307 | 360 | } else { |
308 | 361 | $this->pollStore->loadTotals( $questions_set ); |
— | — | @@ -309,7 +362,7 @@ |
310 | 363 | $this->pollStore->calculateStatistics(); |
311 | 364 | } |
312 | 365 | } |
313 | | - |
| 366 | + |
314 | 367 | # Convert a question on the page from QPoll syntax to HTML |
315 | 368 | # @param $header : the text of question "main" header (common question and XML-like attrs) |
316 | 369 | # $body : the text of question body (starting with body header which defines categories and spans, followed by proposal list) |
— | — | @@ -343,35 +396,35 @@ |
344 | 397 | $question->view->addHeaderError(); |
345 | 398 | # http get: invalid question syntax, parse errors will cause submit button disabled |
346 | 399 | $this->pollStore->stateError(); |
| 400 | + return; |
| 401 | + } |
| 402 | + # populate $question with raw source values |
| 403 | + $question->getQuestionAnswer( $this->pollStore ); |
| 404 | + # check whether the global showresults level prohibits to show statistical data |
| 405 | + # to the users who hasn't voted |
| 406 | + if ( qp_Setup::$global_showresults <= 1 && !$question->alreadyVoted ) { |
| 407 | + # suppress statistical results when the current user hasn't voted the question |
| 408 | + $question->view->showResults = array( 'type'=>0 ); |
| 409 | + } |
| 410 | + # parse the question body |
| 411 | + # will populate $question->view which can be modified accodring to quiz results |
| 412 | + # warning! parameters are passed only by value, not the reference |
| 413 | + $question->{$question->mType . 'ParseBody'}(); |
| 414 | + if ( $this->mBeingCorrected ) { |
| 415 | + if ( $question->getState() == '' ) { |
| 416 | + # question is OK, store it into pollStore |
| 417 | + $question->store( $this->pollStore ); |
| 418 | + } else { |
| 419 | + # http post: not every proposals were answered: do not update DB |
| 420 | + $this->pollStore->stateIncomplete(); |
| 421 | + } |
347 | 422 | } else { |
348 | | - # populate $question with raw source values |
349 | | - $question->getQuestionAnswer( $this->pollStore ); |
350 | | - # check whether the global showresults level prohibits to show statistical data |
351 | | - # to the users who hasn't voted |
352 | | - if ( qp_Setup::$global_showresults <= 1 && !$question->alreadyVoted ) { |
353 | | - # suppress statistical results when the current user hasn't voted the question |
354 | | - $question->view->showResults = array( 'type'=>0 ); |
355 | | - } |
356 | | - # parse the question body |
357 | | - # will populate $question->view which can be modified accodring to quiz results |
358 | | - # warning! parameters are passed only by value, not the reference |
359 | | - $question->{$question->mType . 'ParseBody'}(); |
360 | | - if ( $this->mBeingCorrected ) { |
361 | | - if ( $question->getState() == '' ) { |
362 | | - # question is OK, store it into pollStore |
363 | | - $question->store( $this->pollStore ); |
364 | | - } else { |
365 | | - # http post: not every proposals were answered: do not update DB |
366 | | - $this->pollStore->stateIncomplete(); |
367 | | - } |
| 423 | + # this is the get, not the post: do not update DB |
| 424 | + if ( $question->getState() == '' ) { |
| 425 | + $this->pollStore->stateIncomplete(); |
368 | 426 | } else { |
369 | | - # this is the get, not the post: do not update DB |
370 | | - if ( $question->getState() == '' ) { |
371 | | - $this->pollStore->stateIncomplete(); |
372 | | - } else { |
373 | | - # http get: invalid question syntax, parse errors will cause submit button disabled |
374 | | - $this->pollStore->stateError(); |
375 | | - } |
| 427 | + # http get: invalid question syntax, parse errors will cause submit button disabled |
| 428 | + $this->pollStore->stateError(); |
376 | 429 | } |
377 | 430 | } |
378 | 431 | } |
Index: trunk/extensions/QPoll/qp_user.php |
— | — | @@ -251,6 +251,9 @@ |
252 | 252 | self::autoLoad( array( |
253 | 253 | 'qp_user.php' => array( 'FormatJson', 'qp_Setup', 'qp_Renderer', 'qp_FunctionsHook' ), |
254 | 254 | |
| 255 | + ## collection of the questions |
| 256 | + 'qp_question_collection.php' => 'qp_QuestionCollection', |
| 257 | + |
255 | 258 | ## controllers (polls and questions derived from separate abstract classes) |
256 | 259 | # polls |
257 | 260 | 'ctrl/qp_abstractpoll.php' => 'qp_AbstractPoll', |
— | — | @@ -276,7 +279,7 @@ |
277 | 280 | 'qp_pollstore.php' => array( 'qp_QuestionData', 'qp_InterpAnswer', 'qp_PollStore' ), |
278 | 281 | |
279 | 282 | # results page |
280 | | - 'qp_results.php' => array( 'qp_QueryPage', 'PollResults' ), |
| 283 | + 'qp_results.php' => array( 'qp_SpecialPage', 'qp_QueryPage', 'PollResults' ), |
281 | 284 | |
282 | 285 | # interpretation of answers |
283 | 286 | 'qp_interpret.php' => 'qp_Interpret', |
Index: trunk/extensions/QPoll/qp_question_collection.php |
— | — | @@ -0,0 +1,177 @@ |
| 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_QuestionCollection { |
| 42 | + |
| 43 | + # array of question objects associated with current poll |
| 44 | + private $questions = array(); |
| 45 | + # array of $this->questions[] indexes for question iterator (used by randomizer) |
| 46 | + private $usedQuestions = false; |
| 47 | + |
| 48 | + /** |
| 49 | + * From http://php.net/manual/en/function.mt-rand.php |
| 50 | + * function returns a random integer between min and max, just like function rand() does. |
| 51 | + * Difference to rand is that the random generated number will not use any of the values |
| 52 | + * placed in $except. ($except must therefore be an array) |
| 53 | + * function returns false if $except holds all values between $min and $max. |
| 54 | + */ |
| 55 | + function rand_except( $min, $max, $except ) { |
| 56 | + # first sort array values |
| 57 | + sort( $except, SORT_NUMERIC ); |
| 58 | + # calculate average gap between except-values |
| 59 | + $except_count = count( $except ); |
| 60 | + $avg_gap = ($max - $min + 1 - $except_count) / ($except_count + 1); |
| 61 | + if ( $avg_gap <= 0 ) { |
| 62 | + return false; |
| 63 | + } |
| 64 | + # now add min and max to $except, so all gaps between $except-values can be calculated |
| 65 | + array_unshift( $except, $min - 1 ); |
| 66 | + array_push( $except, $max + 1 ); |
| 67 | + $except_count += 2; |
| 68 | + # iterate through all values of except. If gap between 2 values is higher than average gap, |
| 69 | + # create random in this gap |
| 70 | + for ($i = 1; $i < $except_count; $i++) { |
| 71 | + if ( $except[$i] - $except[$i - 1] - 1 >= $avg_gap ) { |
| 72 | + return mt_rand( $except[$i - 1] + 1, $except[$i] - 1 ); |
| 73 | + } |
| 74 | + } |
| 75 | + return false; |
| 76 | + } |
| 77 | + |
| 78 | + function randomize( $randomQuestionCount ) { |
| 79 | + $questionCount = count( $this->questions ); |
| 80 | + if ( $randomQuestionCount > $questionCount ) { |
| 81 | + $randomQuestionCount = $questionCount; |
| 82 | + } |
| 83 | + $this->usedQuestions = array(); |
| 84 | + for ( $i = 0; $i < $randomQuestionCount; $i++ ) { |
| 85 | + if ( ( $r = $this->rand_except( 1, $questionCount, $this->usedQuestions ) ) === false ) { |
| 86 | + throw new MWException( 'Bug: too many random questions in ' . __METHOD__ ); |
| 87 | + } |
| 88 | + $this->usedQuestions[] = $r; |
| 89 | + } |
| 90 | + sort( $this->usedQuestions, SORT_NUMERIC ); |
| 91 | + if ( count( $this->usedQuestions ) === 0 ) { |
| 92 | + $this->usedQuestions = false; |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + function getUsedQuestions() { |
| 97 | + return $this->usedQuestions; |
| 98 | + } |
| 99 | + |
| 100 | + function setUsedQuestions( $randomQuestions ) { |
| 101 | + if ( !is_array( $randomQuestions ) ) { |
| 102 | + foreach ( $this->questions as $qidx => &$question ) { |
| 103 | + $question->usedId = $question->mQuestionId; |
| 104 | + } |
| 105 | + return; |
| 106 | + } |
| 107 | + sort( $randomQuestions, SORT_NUMERIC ); |
| 108 | + $this->usedQuestions = array(); |
| 109 | + # questions count from 1 |
| 110 | + $usedId = 1; |
| 111 | + foreach ( $this->questions as $qidx => &$question ) { |
| 112 | + if ( in_array( $qidx, $randomQuestions, true ) ) { |
| 113 | + $this->usedQuestions[] = $qidx; |
| 114 | + $question->usedId = $usedId++; |
| 115 | + } else { |
| 116 | + $question->usedId = false; |
| 117 | + } |
| 118 | + } |
| 119 | + if ( count( $this->usedQuestions ) === 0 ) { |
| 120 | + throw new MWException( 'At least one question should not be unused in ' . __METHOD__ ); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + function add( qp_AbstractQuestion $question ) { |
| 125 | + if ( count( $this->questions ) === 0 ) { |
| 126 | + $this->questions[1] = $question; |
| 127 | + } else { |
| 128 | + $this->questions[] = $question; |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + function totalCount() { |
| 133 | + return count( $this->questions ); |
| 134 | + } |
| 135 | + |
| 136 | + function usedCount() { |
| 137 | + $used = 0; |
| 138 | + foreach ( $this->questions as &$question ) { |
| 139 | + if ( $question->usedId !== false ) { |
| 140 | + $used++; |
| 141 | + } |
| 142 | + } |
| 143 | + return $used; |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Reset question iterator |
| 148 | + */ |
| 149 | + function reset() { |
| 150 | + reset( $this->questions ); |
| 151 | + if ( is_array( $this->usedQuestions ) ) { |
| 152 | + reset( $this->usedQuestions ); |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + /** |
| 157 | + * Get current question and rewind to the next question |
| 158 | + * @return instance of qp_AbstractQuestion or derivative or |
| 159 | + * boolean false - when there are no more questions left |
| 160 | + */ |
| 161 | + function iterate() { |
| 162 | + if ( is_array( $this->usedQuestions ) ) { |
| 163 | + while ( !is_null( key( $this->usedQuestions ) ) ) { |
| 164 | + list( $key, $qidx ) = each( $this->usedQuestions ); |
| 165 | + if ( isset( $this->questions[$qidx] ) ) { |
| 166 | + return $this->questions[$qidx]; |
| 167 | + } |
| 168 | + } |
| 169 | + return false; |
| 170 | + } |
| 171 | + if ( !is_null( key( $this->questions ) ) ) { |
| 172 | + list( $key, $question ) = each( $this->questions ); |
| 173 | + return $question; |
| 174 | + } |
| 175 | + return false; |
| 176 | + } |
| 177 | + |
| 178 | +} |
Property changes on: trunk/extensions/QPoll/qp_question_collection.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 179 | + native |
Index: trunk/extensions/QPoll/qp_pollstore.php |
— | — | @@ -184,6 +184,8 @@ |
185 | 185 | /// DB keys |
186 | 186 | var $pid = null; |
187 | 187 | var $last_uid = null; |
| 188 | + # username is used for caching of setLastUser() method (which now may be called multiple times) |
| 189 | + var $username = ''; |
188 | 190 | /// common properties |
189 | 191 | var $mArticleId = null; |
190 | 192 | # unique id of poll, used for addressing, also with 'qp_' prefix as the fragment part of the link |
— | — | @@ -205,6 +207,9 @@ |
206 | 208 | |
207 | 209 | # array of QuestionData instances (data from/to DB) |
208 | 210 | var $Questions = null; |
| 211 | + # array of random indexes of Questions[] array (optional) |
| 212 | + var $randomQuestions = false; |
| 213 | + |
209 | 214 | # poll processing state, read with getState() |
210 | 215 | # |
211 | 216 | # 'NA' - object just was created |
— | — | @@ -673,10 +678,56 @@ |
674 | 679 | } |
675 | 680 | } |
676 | 681 | |
| 682 | + function isUsedQuestion( $question_id ) { |
| 683 | + return !is_array( $this->randomQuestions ) || |
| 684 | + in_array( $question_id, $this->randomQuestions, true ); |
| 685 | + } |
| 686 | + |
| 687 | + function loadRandomQuestions( $username ) { |
| 688 | + if ( is_null( $this->pid ) ) { |
| 689 | + $this->setPid(); |
| 690 | + } |
| 691 | + $this->setLastUser( $username ); |
| 692 | + $res = self::$db->select( 'qp_random_questions', 'question_id', array( 'uid' => $this->last_uid, 'pid' => $this->pid ), __METHOD__ ); |
| 693 | + $this->randomQuestions = array(); |
| 694 | + while ( $row = self::$db->fetchObject( $res ) ) { |
| 695 | + $this->randomQuestions[] = intval( $row->question_id ); |
| 696 | + } |
| 697 | + if ( count( $this->randomQuestions ) === 0 ) { |
| 698 | + $this->randomQuestions = false; |
| 699 | + } |
| 700 | + } |
| 701 | + |
| 702 | + function setRandomQuestions() { |
| 703 | + if ( is_null( $this->pid ) || is_null( $this->last_uid ) ) { |
| 704 | + throw new MWException( __METHOD__ . ' cannot be called when pid/uid was not set' ); |
| 705 | + } |
| 706 | + if ( is_array( $this->randomQuestions ) ) { |
| 707 | + $data = array(); |
| 708 | + foreach( $this->randomQuestions as $qidx ) { |
| 709 | + $data[] = array( 'pid' => $this->pid, 'uid' => $this->last_uid, 'question_id' => $qidx ); |
| 710 | + } |
| 711 | + $res = self::$db->replace( 'qp_random_questions', |
| 712 | + 'user_poll_question', |
| 713 | + $data, |
| 714 | + __METHOD__ . ':random questions seed update' ); |
| 715 | + return; |
| 716 | + } |
| 717 | + self::$db->delete( 'qp_random_questions', |
| 718 | + array( 'pid'=>$this->pid ), |
| 719 | + __METHOD__ . ':remove question random seed' |
| 720 | + ); |
| 721 | + } |
| 722 | + |
677 | 723 | function setLastUser( $username, $store_new_user_to_db = true ) { |
678 | 724 | if ( $this->pid === null ) { |
679 | 725 | return; |
680 | 726 | } |
| 727 | + # do no query DB for the same user more than once |
| 728 | + if ( $this->username === $username ) { |
| 729 | + return; |
| 730 | + } |
| 731 | + $this->username = $username; |
681 | 732 | $res = self::$db->select( 'qp_users','uid','name=' . self::$db->addQuotes( $username ), __METHOD__ ); |
682 | 733 | $row = self::$db->fetchObject( $res ); |
683 | 734 | if ( $row == false ) { |
— | — | @@ -768,7 +819,7 @@ |
769 | 820 | ); |
770 | 821 | } |
771 | 822 | } |
772 | | - |
| 823 | + |
773 | 824 | private function setQuestionDesc() { |
774 | 825 | $insert = array(); |
775 | 826 | foreach ( $this->Questions as $qkey => &$ques ) { |
Index: trunk/extensions/QPoll/tables/qpoll_random_questions.src |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +CREATE TABLE /*$wgDBprefix*/qp_random_questions ( |
| 3 | + `uid` int unsigned NOT NULL, |
| 4 | + `pid` int unsigned NOT NULL, |
| 5 | + `question_id` int unsigned NOT NULL, |
| 6 | + PRIMARY KEY user_poll_question (uid,pid,question_id) |
| 7 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Index: trunk/extensions/QPoll/tables/qpoll_trunk.sql |
— | — | @@ -78,3 +78,11 @@ |
79 | 79 | INDEX user_id (uid), |
80 | 80 | INDEX username (name(64)) |
81 | 81 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
| 82 | + |
| 83 | +DROP TABLE IF EXISTS `qp_random_questions`; |
| 84 | +CREATE TABLE `qp_random_questions` ( |
| 85 | + `uid` int unsigned NOT NULL, |
| 86 | + `pid` int unsigned NOT NULL, |
| 87 | + `question_id` int unsigned NOT NULL, |
| 88 | + PRIMARY KEY user_poll_question (uid,pid,question_id) |
| 89 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Index: trunk/extensions/QPoll/view/qp_pollstatsview.php |
— | — | @@ -64,7 +64,8 @@ |
65 | 65 | $write_row = array(); |
66 | 66 | $write_col = array(); |
67 | 67 | # render the body |
68 | | - foreach ( $this->ctrl->questions as &$question ) { |
| 68 | + $this->ctrl->questions->reset(); |
| 69 | + while ( is_object( $question = $this->ctrl->questions->iterate() ) ) { |
69 | 70 | # render the question statistics only when showResuls isn't 0 (suppress stats) |
70 | 71 | if ( $question->view->showResults['type'] != 0 ) { |
71 | 72 | if ( $this->perRow > 1 ) { |
Index: trunk/extensions/QPoll/view/qp_pollview.php |
— | — | @@ -64,7 +64,8 @@ |
65 | 65 | $write_row = array(); |
66 | 66 | $write_col = array(); |
67 | 67 | # render the body |
68 | | - foreach ( $this->ctrl->questions as &$question ) { |
| 68 | + $this->ctrl->questions->reset(); |
| 69 | + while ( is_object( $question = $this->ctrl->questions->iterate() ) ) { |
69 | 70 | $question->view->renderInterpErrors(); |
70 | 71 | if ( $this->perRow > 1 ) { |
71 | 72 | $write_col[] = array( '__tag'=>'td', 'valign'=>'top', 0=>$question->view->renderQuestion(), '__end'=>"\n" ); |
Index: trunk/extensions/QPoll/view/qp_questionview.php |
— | — | @@ -419,7 +419,7 @@ |
420 | 420 | $output_table[] = array( '__tag'=>'tbody', '__end'=>"\n", 0=>$this->renderTable() ); |
421 | 421 | $tags = array( '__tag'=>'div', '__end'=>"\n", 'class'=>'question', |
422 | 422 | 0=>array( '__tag'=>'div', '__end'=>"\n", 'class'=>'header', |
423 | | - 0=>array( '__tag'=>'span', 'class'=>'questionId', 0=>$this->ctrl->mQuestionId ) |
| 423 | + 0=>array( '__tag'=>'span', 'class'=>'questionId', 0=>$this->ctrl->usedId ) |
424 | 424 | ), |
425 | 425 | 1=>array( '__tag'=>'div', 0=>$this->rtp( $this->ctrl->mCommonQuestion ) ) |
426 | 426 | ); |