Index: trunk/extensions/QPoll/qp_results.php |
— | — | @@ -348,7 +348,6 @@ |
349 | 349 | $userTitle = Title::makeTitleSafe( NS_USER, $userName ); |
350 | 350 | $user_link = $this->qpLink( $userTitle, $userName ); |
351 | 351 | $pollStore->setLastUser( $userName, false ); |
352 | | - $pollStore->loadRandomQuestions(); |
353 | 352 | if ( !$pollStore->loadUserVote() ) { |
354 | 353 | return ''; |
355 | 354 | } |
Index: trunk/extensions/QPoll/archives/qpoll_interpretation.src |
— | — | @@ -1,8 +1,9 @@ |
| 2 | +-- fields to store the interpretation template title and random questions count |
2 | 3 | ALTER TABLE /*$wgDBprefix*/qp_poll_desc |
3 | 4 | DROP INDEX article_poll, |
4 | 5 | ADD COLUMN interpretation_namespace int NOT NULL default 0, |
5 | 6 | ADD COLUMN interpretation_title varchar(255) binary NOT NULL default '', |
| 7 | + ADD COLUMN random_question_count int NOT NULL default 0, |
6 | 8 | ADD UNIQUE INDEX article_poll (article_id,poll_id(128)); |
7 | 9 | |
8 | 10 | -- fields to store the interpretation results |
Index: trunk/extensions/QPoll/ctrl/qp_poll.php |
— | — | @@ -127,10 +127,11 @@ |
128 | 128 | } |
129 | 129 | } |
130 | 130 | $newPollStore = array( |
131 | | - 'poll_id'=>$this->mPollId, |
132 | | - 'order_id'=>$this->mOrderId, |
133 | | - 'dependance'=>$this->dependsOn, |
134 | | - 'interpretation'=>$this->interpretation |
| 131 | + 'poll_id' => $this->mPollId, |
| 132 | + 'order_id' => $this->mOrderId, |
| 133 | + 'randomQuestionCount' => $this->randomQuestionCount, |
| 134 | + 'dependance' => $this->dependsOn, |
| 135 | + 'interpretation' => $this->interpretation |
135 | 136 | ); |
136 | 137 | if ( ( $dependanceResult = $this->checkDependance( $this->dependsOn ) ) !== true ) { |
137 | 138 | # return an error string |
— | — | @@ -173,34 +174,22 @@ |
174 | 175 | } |
175 | 176 | |
176 | 177 | function setUsedQuestions() { |
177 | | - # todo: make global settings to perform all of this conditionally |
178 | | - # load random questions from DB (when available) |
179 | | - # setLastUser will not load/store user data, when pid is null |
180 | | - $this->pollStore->setLastUser( $this->username ); |
181 | | - # loadRandomQuestions will call setPid() and setLastUser() in such case |
182 | | - $this->pollStore->loadRandomQuestions(); |
183 | | - if ( $this->randomQuestionCount > 0 ) { |
184 | | - if ( $this->randomQuestionCount > $this->questions->totalCount() ) { |
185 | | - $this->randomQuestionCount = $this->questions->totalCount(); |
186 | | - } |
187 | | - if ( is_array( $this->pollStore->randomQuestions ) ) { |
188 | | - if ( count( $this->pollStore->randomQuestions ) == $this->randomQuestionCount ) { |
189 | | - # count of random questions was not changed, no need to regenerate seed |
190 | | - $this->questions->setUsedQuestions( $this->pollStore->randomQuestions ); |
191 | | - return; |
192 | | - } |
193 | | - } |
194 | | - # generate or regenerate random questions |
195 | | - $this->questions->randomize( $this->randomQuestionCount ); |
196 | | - $this->pollStore->randomQuestions = $this->questions->getUsedQuestions(); |
197 | | - } else { |
198 | | - if ( !is_array( $this->pollStore->randomQuestions ) ) { |
199 | | - # random questions are disabled and no previous seed in DB |
| 178 | + if ( $this->randomQuestionCount === 0 ) { |
| 179 | + return; |
| 180 | + } |
| 181 | + if ( $this->randomQuestionCount > $this->questions->totalCount() ) { |
| 182 | + $this->randomQuestionCount = $this->questions->totalCount(); |
| 183 | + } |
| 184 | + if ( is_array( $this->pollStore->randomQuestions ) ) { |
| 185 | + if ( count( $this->pollStore->randomQuestions ) == $this->randomQuestionCount ) { |
| 186 | + # count of random questions was not changed, no need to regenerate seed |
| 187 | + $this->questions->setUsedQuestions( $this->pollStore->randomQuestions ); |
200 | 188 | return; |
201 | 189 | } |
202 | | - # there was stored random seed, will remove it at the end of this function |
203 | | - $this->pollStore->randomQuestions = false; |
204 | 190 | } |
| 191 | + # generate or regenerate random questions |
| 192 | + $this->questions->randomize( $this->randomQuestionCount ); |
| 193 | + $this->pollStore->randomQuestions = $this->questions->getUsedQuestions(); |
205 | 194 | # store random questions into DB |
206 | 195 | $this->pollStore->setRandomQuestions(); |
207 | 196 | } |
Index: trunk/extensions/QPoll/qp_pollstore.php |
— | — | @@ -184,6 +184,10 @@ |
185 | 185 | class qp_PollStore { |
186 | 186 | |
187 | 187 | static $db = null; |
| 188 | + # indicates whether random questions must be erased / regenerated when the value of |
| 189 | + # 'randomize' attribute is changed from non-zero to zero and back |
| 190 | + static $purgeRandomQuestions = false; |
| 191 | + |
188 | 192 | /// DB keys |
189 | 193 | var $pid = null; |
190 | 194 | var $last_uid = null; |
— | — | @@ -191,30 +195,37 @@ |
192 | 196 | # username is used for caching of setLastUser() method (which now may be called multiple times); |
193 | 197 | # also used by randomizer |
194 | 198 | var $username = ''; |
195 | | - /// common properties |
| 199 | + |
| 200 | + /*** common properties ***/ |
196 | 201 | var $mArticleId = null; |
197 | 202 | # unique id of poll, used for addressing, also with 'qp_' prefix as the fragment part of the link |
198 | 203 | var $mPollId = null; |
199 | 204 | # order of poll on the page |
200 | 205 | var $mOrderId = null; |
| 206 | + |
| 207 | + /*** optional attributes ***/ |
201 | 208 | # dependance from other poll address in the following format: "page#otherpollid" |
202 | 209 | var $dependsOn = null; |
203 | | - # attempts of voting (passing the quiz). number of resubmits |
204 | | - # note: resubmits are counted for syntax-correct answer (the vote is stored), |
205 | | - # yet the answer still might be logically incorrect (quiz is not passed / partially passed) |
206 | | - var $attempts = 0; |
207 | | - |
208 | 210 | # NS & DBkey of Title object representing interpretation template for Special:Pollresults page |
209 | 211 | var $interpNS = 0; |
210 | 212 | var $interpDBkey = null; |
211 | 213 | # interpretation of user answer |
212 | 214 | var $interpResult; |
| 215 | + # 1..n - number of random indexes from poll's header; 0 - poll questions are not randomized |
| 216 | + # pollstore loads / saves random indexes for every user only when this property is NOT zero |
| 217 | + # which improves performance of non-randomized polls |
| 218 | + var $randomQuestionCount = null; |
213 | 219 | |
214 | 220 | # array of QuestionData instances (data from/to DB) |
215 | 221 | var $Questions = null; |
216 | 222 | # array of random indexes of Questions[] array (optional) |
217 | 223 | var $randomQuestions = false; |
218 | 224 | |
| 225 | + # attempts of voting (passing the quiz). number of resubmits |
| 226 | + # note: resubmits are counted for syntax-correct answer (when the vote is stored), |
| 227 | + # yet the answer still might be logically incorrect (quiz is not passed / partially passed) |
| 228 | + var $attempts = 0; |
| 229 | + |
219 | 230 | # poll processing state, read with getState() |
220 | 231 | # |
221 | 232 | # 'NA' - object just was created |
— | — | @@ -277,29 +288,42 @@ |
278 | 289 | } |
279 | 290 | } |
280 | 291 | } |
281 | | - if ( $is_post ) { |
282 | | - $this->setPid(); |
283 | | - } else { |
284 | | - $this->loadPid(); |
| 292 | + if ( array_key_exists( 'randomQuestionCount', $argv ) ) { |
| 293 | + $this->randomQuestionCount = $argv['randomQuestionCount']; |
285 | 294 | } |
| 295 | + # do not load / create the poll when article id is unavailable |
| 296 | + # (only during newly created page submission) |
| 297 | + if ( $this->mArticleId != 0 ) { |
| 298 | + if ( $is_post ) { |
| 299 | + $this->setPid(); |
| 300 | + } else { |
| 301 | + $this->loadPid(); |
| 302 | + if ( is_null( $this->pid ) ) { |
| 303 | + # try to create poll description (DB state was incomplete) |
| 304 | + $this->setPid(); |
| 305 | + } |
| 306 | + } |
| 307 | + } |
286 | 308 | break; |
287 | 309 | case 'pid' : |
288 | 310 | if ( array_key_exists( 'pid', $argv ) ) { |
289 | 311 | $pid = intval( $argv[ 'pid' ] ); |
290 | 312 | $res = self::$db->select( 'qp_poll_desc', |
291 | | - array( 'article_id', 'poll_id','order_id', 'dependance', 'interpretation_namespace', 'interpretation_title' ), |
| 313 | + array( 'article_id', 'poll_id','order_id', 'dependance', 'interpretation_namespace', 'interpretation_title', 'random_question_count' ), |
292 | 314 | array( 'pid'=>$pid ), |
293 | 315 | __METHOD__ . ":create from pid" ); |
294 | 316 | $row = self::$db->fetchObject( $res ); |
295 | | - if ( $row!=false ) { |
296 | | - $this->pid = $pid; |
297 | | - $this->mArticleId = $row->article_id; |
298 | | - $this->mPollId = $row->poll_id; |
299 | | - $this->mOrderId = $row->order_id; |
300 | | - $this->dependsOn = $row->dependance; |
301 | | - $this->interpNS = $row->interpretation_namespace; |
302 | | - $this->interpDBkey = $row->interpretation_title; |
| 317 | + if ( $row === false ) { |
| 318 | + throw new MWException( 'Attempt to create poll from non-existent poll id in ' . __METHOD__ ); |
303 | 319 | } |
| 320 | + $this->pid = $pid; |
| 321 | + $this->mArticleId = $row->article_id; |
| 322 | + $this->mPollId = $row->poll_id; |
| 323 | + $this->mOrderId = $row->order_id; |
| 324 | + $this->dependsOn = $row->dependance; |
| 325 | + $this->interpNS = $row->interpretation_namespace; |
| 326 | + $this->interpDBkey = $row->interpretation_title; |
| 327 | + $this->randomQuestionCount = $row->random_question_count; |
304 | 328 | } |
305 | 329 | break; |
306 | 330 | } |
— | — | @@ -338,11 +362,20 @@ |
339 | 363 | |
340 | 364 | # returns Title object, to get a URI path, use Title::getFullText()/getPrefixedText() on it |
341 | 365 | function getTitle() { |
342 | | - $res = null; |
343 | | - if ( $this->mArticleId !==null && $this->mPollId !== null ) { |
344 | | - $res = Title::newFromID( $this->mArticleId ); |
345 | | - $res->setFragment( qp_AbstractPoll::s_getPollTitleFragment( $this->mPollId ) ); |
| 366 | + if ( $this->mArticleId === 0 ) { |
| 367 | + throw new MWException( __METHOD__ . ' cannot be called for unsaved new pages' ); |
346 | 368 | } |
| 369 | + if ( is_null( $this->mArticleId ) ) { |
| 370 | + throw new MWException( 'Unknown article id in ' . __METHOD__ ); |
| 371 | + } |
| 372 | + if ( is_null( $this->mPollId ) ) { |
| 373 | + throw new MWException( 'Unknown poll id in ' . __METHOD__ ); |
| 374 | + } |
| 375 | + $res = Title::newFromID( $this->mArticleId ); |
| 376 | + $res->setFragment( qp_AbstractPoll::s_getPollTitleFragment( $this->mPollId ) ); |
| 377 | + if ( !( $res instanceof Title ) ) { |
| 378 | + throw new MWException( 'Invalid title created in ' . __METHOD__ ); |
| 379 | + } |
347 | 380 | return $res; |
348 | 381 | } |
349 | 382 | |
— | — | @@ -699,9 +732,15 @@ |
700 | 733 | * Will be overriden in memory when number of random questions was changed |
701 | 734 | */ |
702 | 735 | function loadRandomQuestions() { |
| 736 | + if ( $this->mArticleId == 0 ) { |
| 737 | + $this->randomQuestions = false; |
| 738 | + return; |
| 739 | + } |
| 740 | + if ( is_null( $this->pid ) ) { |
| 741 | + throw new MWException( __METHOD__ . ' cannot be called when pid was not set' ); |
| 742 | + } |
703 | 743 | if ( is_null( $this->last_uid ) ) { |
704 | | - $this->setPid(); |
705 | | - $this->setLastUser( $this->username ); |
| 744 | + throw new MWException( __METHOD__ . ' cannot be called when uid was not set' ); |
706 | 745 | } |
707 | 746 | $res = self::$db->select( 'qp_random_questions', 'question_id', array( 'uid' => $this->last_uid, 'pid' => $this->pid ), __METHOD__ ); |
708 | 747 | $this->randomQuestions = array(); |
— | — | @@ -710,6 +749,8 @@ |
711 | 750 | } |
712 | 751 | if ( count( $this->randomQuestions ) === 0 ) { |
713 | 752 | $this->randomQuestions = false; |
| 753 | + } else { |
| 754 | + sort( $this->randomQuestions, SORT_NUMERIC ); |
714 | 755 | } |
715 | 756 | } |
716 | 757 | |
— | — | @@ -720,9 +761,15 @@ |
721 | 762 | * when number of random questions for poll was changed |
722 | 763 | */ |
723 | 764 | function setRandomQuestions() { |
724 | | - if ( is_null( $this->pid ) || is_null( $this->last_uid ) ) { |
725 | | - throw new MWException( __METHOD__ . ' cannot be called when pid/uid was not set' ); |
| 765 | + if ( $this->mArticleId == 0 ) { |
| 766 | + return; |
726 | 767 | } |
| 768 | + if ( is_null( $this->pid ) ) { |
| 769 | + throw new MWException( __METHOD__ . ' cannot be called when pid was not set' ); |
| 770 | + } |
| 771 | + if ( is_null( $this->last_uid ) ) { |
| 772 | + throw new MWException( __METHOD__ . ' cannot be called when uid was not set' ); |
| 773 | + } |
727 | 774 | if ( is_array( $this->randomQuestions ) ) { |
728 | 775 | $data = array(); |
729 | 776 | foreach( $this->randomQuestions as $qidx ) { |
— | — | @@ -740,7 +787,7 @@ |
741 | 788 | } |
742 | 789 | # this->randomQuestions === false; this poll is not randomized anymore |
743 | 790 | self::$db->delete( 'qp_random_questions', |
744 | | - array( 'pid'=>$this->pid ), |
| 791 | + array( 'pid'=>$this->pid, 'uid'=>$this->last_uid ), |
745 | 792 | __METHOD__ . ':remove question random seed' |
746 | 793 | ); |
747 | 794 | } |
— | — | @@ -763,6 +810,7 @@ |
764 | 811 | $this->username = $username; |
765 | 812 | } else { |
766 | 813 | $this->last_uid = null; |
| 814 | + return; |
767 | 815 | } |
768 | 816 | } else { |
769 | 817 | $this->last_uid = intval( $row->uid ); |
— | — | @@ -780,6 +828,10 @@ |
781 | 829 | $this->interpResult->short = $row->short_interpretation; |
782 | 830 | $this->interpResult->long = $row->long_interpretation; |
783 | 831 | } |
| 832 | + $this->randomQuestions = false; |
| 833 | + if ( $this->randomQuestionCount != 0 ) { |
| 834 | + $this->loadRandomQuestions(); |
| 835 | + } |
784 | 836 | // todo: change to "insert ... on duplicate key update ..." when last_insert_id() bugs will be fixed |
785 | 837 | } |
786 | 838 | |
— | — | @@ -795,10 +847,12 @@ |
796 | 848 | } |
797 | 849 | |
798 | 850 | private function loadPid() { |
| 851 | + if ( $this->mArticleId === 0 ) { |
| 852 | + return; |
| 853 | + } |
799 | 854 | $res = self::$db->select( 'qp_poll_desc', |
800 | | - array( 'pid', 'order_id', 'dependance', 'interpretation_namespace', 'interpretation_title' ), |
801 | | - 'article_id=' . self::$db->addQuotes( $this->mArticleId ) . ' and ' . |
802 | | - 'poll_id=' . self::$db->addQuotes( $this->mPollId ), |
| 855 | + array( 'pid', 'order_id', 'dependance', 'interpretation_namespace', 'interpretation_title', 'random_question_count' ), |
| 856 | + array( 'article_id' => $this->mArticleId, 'poll_id' => $this->mPollId ), |
803 | 857 | __METHOD__ ); |
804 | 858 | $row = self::$db->fetchObject( $res ); |
805 | 859 | if ( $row != false ) { |
— | — | @@ -814,19 +868,25 @@ |
815 | 869 | $this->interpNS = $row->interpretation_namespace; |
816 | 870 | $this->interpDBkey = $row->interpretation_title; |
817 | 871 | } |
| 872 | + if ( is_null( $this->randomQuestionCount ) ) { |
| 873 | + $this->randomQuestionCount = $row->random_question_count; |
| 874 | + } |
818 | 875 | $this->updatePollAttributes( $row ); |
819 | 876 | } |
820 | 877 | } |
821 | 878 | |
822 | 879 | private function setPid() { |
| 880 | + if ( $this->mArticleId === 0 ) { |
| 881 | + throw new MWException( 'Cannot save new poll description during new page preprocess in ' . __METHOD__ ); |
| 882 | + } |
823 | 883 | $res = self::$db->select( 'qp_poll_desc', |
824 | | - array( 'pid', 'order_id', 'dependance', 'interpretation_namespace', 'interpretation_title' ), |
| 884 | + array( 'pid', 'order_id', 'dependance', 'interpretation_namespace', 'interpretation_title', 'random_question_count' ), |
825 | 885 | 'article_id=' . self::$db->addQuotes( $this->mArticleId ) . ' and ' . |
826 | 886 | 'poll_id=' . self::$db->addQuotes( $this->mPollId ) ); |
827 | 887 | $row = self::$db->fetchObject( $res ); |
828 | 888 | if ( $row == false ) { |
829 | 889 | self::$db->insert( 'qp_poll_desc', |
830 | | - array( 'article_id'=>$this->mArticleId, 'poll_id'=>$this->mPollId, 'order_id'=>$this->mOrderId, 'dependance'=>$this->dependsOn, 'interpretation_namespace'=>$this->interpNS, 'interpretation_title'=>$this->interpDBkey ), |
| 890 | + array( 'article_id'=>$this->mArticleId, 'poll_id'=>$this->mPollId, 'order_id'=>$this->mOrderId, 'dependance'=>$this->dependsOn, 'interpretation_namespace'=>$this->interpNS, 'interpretation_title'=>$this->interpDBkey, 'random_question_count'=>$this->randomQuestionCount ), |
831 | 891 | __METHOD__ . ':update poll' ); |
832 | 892 | $this->pid = self::$db->insertId(); |
833 | 893 | } else { |
— | — | @@ -837,16 +897,27 @@ |
838 | 898 | } |
839 | 899 | |
840 | 900 | private function updatePollAttributes( $row ) { |
| 901 | + self::$db->begin(); |
841 | 902 | if ( $this->mOrderId != $row->order_id || |
842 | 903 | $this->dependsOn != $row->dependance || |
843 | 904 | $this->interpNS != $row->interpretation_namespace || |
844 | | - $this->interpDBkey != $row->interpretation_title ) { |
| 905 | + $this->interpDBkey != $row->interpretation_title || |
| 906 | + ( $rqcChanged = $this->randomQuestionCount != $row->random_question_count ) ) { |
845 | 907 | $res = self::$db->replace( 'qp_poll_desc', |
846 | 908 | array( 'poll', 'article_poll' ), |
847 | | - array( 'pid'=>$this->pid, 'article_id'=>$this->mArticleId, 'poll_id'=>$this->mPollId, 'order_id'=>$this->mOrderId, 'dependance'=>$this->dependsOn, 'interpretation_namespace'=>$this->interpNS, 'interpretation_title'=>$this->interpDBkey ), |
| 909 | + array( 'pid'=>$this->pid, 'article_id'=>$this->mArticleId, 'poll_id'=>$this->mPollId, 'order_id'=>$this->mOrderId, 'dependance'=>$this->dependsOn, 'interpretation_namespace'=>$this->interpNS, 'interpretation_title'=>$this->interpDBkey, 'random_question_count'=>$this->randomQuestionCount ), |
848 | 910 | __METHOD__ . ':poll attributes update' |
849 | 911 | ); |
850 | 912 | } |
| 913 | + if ( $rqcChanged && |
| 914 | + $this->randomQuestionCount == 0 && |
| 915 | + self::$purgeRandomQuestions ) { |
| 916 | + # the poll questions are not randomized anymore |
| 917 | + self::$db->delete( 'qp_random_questions', |
| 918 | + array( 'pid' => $this->pid ), |
| 919 | + __METHOD__ . ':delete unused random seeds' ); |
| 920 | + } |
| 921 | + self::$db->commit(); |
851 | 922 | } |
852 | 923 | |
853 | 924 | private function setQuestionDesc() { |
Index: trunk/extensions/QPoll/tables/qpoll_random_questions.src |
— | — | @@ -3,5 +3,6 @@ |
4 | 4 | `pid` int unsigned NOT NULL, |
5 | 5 | `question_id` int unsigned NOT NULL, |
6 | 6 | PRIMARY KEY user_poll_question (uid,pid,question_id), |
7 | | - INDEX user_seed (uid,pid) |
| 7 | + INDEX user_seed (uid,pid), |
| 8 | + INDEX poll (pid) |
8 | 9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Index: trunk/extensions/QPoll/tables/qpoll_trunk.sql |
— | — | @@ -12,6 +12,7 @@ |
13 | 13 | `dependance` mediumtext NOT NULL, |
14 | 14 | interpretation_namespace int NOT NULL, |
15 | 15 | interpretation_title varchar(255) binary NOT NULL, |
| 16 | + random_question_count int NOT NULL default 0, |
16 | 17 | PRIMARY KEY poll (pid), |
17 | 18 | UNIQUE INDEX article_poll (article_id,poll_id(128)) |
18 | 19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
— | — | @@ -85,5 +86,6 @@ |
86 | 87 | `pid` int unsigned NOT NULL, |
87 | 88 | `question_id` int unsigned NOT NULL, |
88 | 89 | PRIMARY KEY user_poll_question (uid,pid,question_id), |
89 | | - INDEX user_seed (uid,pid) |
| 90 | + INDEX user_seed (uid,pid), |
| 91 | + INDEX poll (pid) |
90 | 92 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |