Index: branches/wmf/1.18wmf1/extensions/Contest/Contest.alias.php |
— | — | @@ -0,0 +1,26 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Aliases for the special pages of the Contest extension. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file Contest.alias.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3+ |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | + |
| 16 | +$specialPageAliases = array(); |
| 17 | + |
| 18 | +/** English (English) */ |
| 19 | +$specialPageAliases['en'] = array( |
| 20 | + 'Contest' => array( 'Contest' ), |
| 21 | + 'Contestant' => array( 'Contestant' ), |
| 22 | + 'Contests' => array( 'Contests' ), |
| 23 | + 'ContestSignup' => array( 'ContestSignup' ), |
| 24 | + 'ContestWelcome' => array( 'ContestWelcome' ), |
| 25 | + 'EditContest' => array( 'EditContest' ), |
| 26 | + 'MyContests' => array( 'MyContests', 'ContestSubmission', 'My contests' ), |
| 27 | +); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/Contest.alias.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 28 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/README |
— | — | @@ -0,0 +1,42 @@ |
| 2 | +These is the readme file for the Contest extension. |
| 3 | + |
| 4 | +Extension page on mediawiki.org: https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | +Latest version of the readme file: http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/Contest/README?view=co |
| 6 | + |
| 7 | +== About == |
| 8 | + |
| 9 | +Contest is a MediaWiki extension. |
| 10 | + |
| 11 | +=== Feature overview === |
| 12 | + |
| 13 | +* Admin interface for managing contests and their challenges. |
| 14 | +* Landing and signup pages for each contest. |
| 15 | +* Personal contest list and submission interface for each user. |
| 16 | +* Summary pages per contest listing contestants, which can be filtered and sorted. |
| 17 | +* Judging interface that allows for rating and commenting on each participant. |
| 18 | +* All contests, challenges, contestants, comments and votes can be queried and exported via the API. |
| 19 | + |
| 20 | +== Credits to other projects == |
| 21 | + |
| 22 | +=== jQuery === |
| 23 | + |
| 24 | +This extension uses code from the jQuery library. |
| 25 | +jQuery is dual licensed under the MIT [0] and GPL [1] licenses. |
| 26 | + |
| 27 | +=== jQuery UI === |
| 28 | + |
| 29 | +This extension uses code from the jQuery UI library. |
| 30 | +jQuery is dual licensed under the MIT [0] and GPL [1] licenses. |
| 31 | + |
| 32 | +=== Fancybox === |
| 33 | + |
| 34 | +This extension includes code from the FancyBox library. FancyBox is an open-source |
| 35 | +jQuery library dual licensed under the MIT [0] and GPL [1] licenses. |
| 36 | + |
| 37 | +=== jQuery datetimepicker === |
| 38 | + |
| 39 | +This extension includes the jQuery timepicker addon by Trent Richardson. |
| 40 | +The datetimepicker addon is dual licensed under the MIT [0] and GPL [1] licenses. |
| 41 | + |
| 42 | +[0] http://www.opensource.org/licenses/mit-license.php |
| 43 | +[1] http://www.opensource.org/licenses/gpl-license.php |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/README |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 44 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/sql/AddContestEmailFields.sql |
— | — | @@ -0,0 +1,7 @@ |
| 2 | +ALTER TABLE /*_*/contests |
| 3 | + ADD COLUMN contest_signup_email VARCHAR(255) NOT NULL, |
| 4 | + ADD COLUMN contest_reminder_email VARCHAR(255) NOT NULL; |
| 5 | + |
| 6 | +UPDATE /*_*/contests SET |
| 7 | + contest_signup_email = 'MediaWiki:', |
| 8 | + contest_reminder_email = 'MediaWiki:'; |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/sql/AddContestEmailFields.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 9 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestWelcome.php |
— | — | @@ -0,0 +1,226 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contest landing page for participants. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialContestWelcome.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialContestWelcome extends SpecialContestPage { |
| 16 | + |
| 17 | + /** |
| 18 | + * Constructor. |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + */ |
| 22 | + public function __construct() { |
| 23 | + parent::__construct( 'ContestWelcome' ); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Main method. |
| 28 | + * |
| 29 | + * @since 0.1 |
| 30 | + * |
| 31 | + * @param string $arg |
| 32 | + */ |
| 33 | + public function execute( $subPage ) { |
| 34 | + $subPage = str_replace( '_', ' ', $subPage ); |
| 35 | + |
| 36 | + if ( !parent::execute( $subPage ) ) { |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + $out = $this->getOutput(); |
| 41 | + |
| 42 | + $contest = Contest::s()->selectRow( null, array( 'name' => $subPage ) ); |
| 43 | + |
| 44 | + /** |
| 45 | + * @var $contest Contest |
| 46 | + */ |
| 47 | + |
| 48 | + if ( $contest === false ) { |
| 49 | + $this->showError( 'contest-welcome-unknown' ); |
| 50 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 51 | + $out->returnToMain(); |
| 52 | + } |
| 53 | + else if ( ( $contest->getStatus() == Contest::STATUS_FINISHED ) || |
| 54 | + ( $contest->getStatus() == Contest::STATUS_EXPIRED ) ) { |
| 55 | + $this->showWarning( 'contest-signup-finished' ); |
| 56 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 57 | + $out->returnToMain(); |
| 58 | + } else if ( $contest->getStatus() == Contest::STATUS_DRAFT ) { |
| 59 | + $this->showWarning( 'contest-signup-draft' ); |
| 60 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 61 | + $out->returnToMain(); |
| 62 | + } |
| 63 | + else { |
| 64 | + $this->showEnabledPage( $contest ); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + protected function showEnabledPage( Contest $contest ) { |
| 69 | + $out = $this->getOutput(); |
| 70 | + |
| 71 | + $alreadySignedup = $this->getUser()->isLoggedIn(); |
| 72 | + |
| 73 | + if ( $alreadySignedup ) { |
| 74 | + // Check if the user is already a contestant in this contest. |
| 75 | + // If he is, reirect to submission page, else show signup form. |
| 76 | + $alreadySignedup = ContestContestant::s()->selectRow( |
| 77 | + 'id', |
| 78 | + array( |
| 79 | + 'contest_id' => $contest->getId(), |
| 80 | + 'user_id' => $this->getUser()->getId() |
| 81 | + ) |
| 82 | + ) !== false; |
| 83 | + } |
| 84 | + |
| 85 | + if ( $alreadySignedup ) { |
| 86 | + $out->redirect( SpecialPage::getTitleFor( 'MyContests', $contest->getField( 'name' ) )->getLocalURL() ); |
| 87 | + } |
| 88 | + else { |
| 89 | + $out->setPageTitle( $contest->getField( 'name' ) ); |
| 90 | + |
| 91 | + $this->showIntro( $contest ); |
| 92 | + $this->showChallenges( $contest ); |
| 93 | + $this->showOpportunities( $contest ); |
| 94 | + $this->showRules( $contest ); |
| 95 | + |
| 96 | + $out->addModules( 'contest.special.welcome' ); |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * Show the intro text for this contest. |
| 102 | + * |
| 103 | + * @since 0.1 |
| 104 | + * |
| 105 | + * @param Contest $contest |
| 106 | + */ |
| 107 | + protected function showIntro( Contest $contest ) { |
| 108 | + $this->getOutput()->addWikiText( ContestUtils::getArticleContent( $contest->getField( 'intro' ) ) ); |
| 109 | + } |
| 110 | + |
| 111 | + /** |
| 112 | + * Show a list of the challenges part of this contest. |
| 113 | + * |
| 114 | + * @since 0.1 |
| 115 | + * |
| 116 | + * @param Contest $contest |
| 117 | + */ |
| 118 | + protected function showChallenges( Contest $contest ) { |
| 119 | + $this->showNoJSFallback( $contest ); |
| 120 | + |
| 121 | + $this->getOutput()->addHTML( '<div id="contest-challenges"></div><div style="clear:both"></div>' ); |
| 122 | + |
| 123 | + $this->addContestJS( $contest ); |
| 124 | + } |
| 125 | + |
| 126 | + /** |
| 127 | + * Output the needed JS data. |
| 128 | + * |
| 129 | + * @since 0.1 |
| 130 | + * |
| 131 | + * @param Contest $contest |
| 132 | + */ |
| 133 | + protected function addContestJS( Contest $contest ) { |
| 134 | + $challenges = array(); |
| 135 | + |
| 136 | + foreach ( $contest->getChallenges() as /* ContestChallenge */ $challenge ) { |
| 137 | + $data = $challenge->toArray(); |
| 138 | + $data['target'] = $this->getSignupLink( $contest->getField( 'name' ), $challenge->getId() ); |
| 139 | + $challenges[] = $data; |
| 140 | + } |
| 141 | + |
| 142 | + $this->getOutput()->addScript( |
| 143 | + Skin::makeVariablesScript( |
| 144 | + array( |
| 145 | + 'ContestChallenges' => $challenges, |
| 146 | + 'ContestConfig' => array() |
| 147 | + ) |
| 148 | + ) |
| 149 | + ); |
| 150 | + } |
| 151 | + |
| 152 | + /** |
| 153 | + * Output fallback code for people that have JS disabled or have a crappy browser. |
| 154 | + * |
| 155 | + * @since 0.1 |
| 156 | + * |
| 157 | + * @param Contest $contest |
| 158 | + */ |
| 159 | + protected function showNoJSFallback( Contest $contest ) { |
| 160 | + $out = $this->getOutput(); |
| 161 | + |
| 162 | + $out->addHTML( '<noscript>' ); |
| 163 | + $out->addHTML( '<p class="errorbox">' . htmlspecialchars( wfMsg( 'contest-welcome-js-off' ) ) . '</p>' ); |
| 164 | + // TODO? |
| 165 | + $out->addHTML( '</noscript>' ); |
| 166 | + } |
| 167 | + |
| 168 | + /** |
| 169 | + * Show the opportunities for this contest. |
| 170 | + * |
| 171 | + * @since 0.1 |
| 172 | + * |
| 173 | + * @param Contest $contest |
| 174 | + */ |
| 175 | + protected function showOpportunities( Contest $contest ) { |
| 176 | + $this->getOutput()->addWikiText( ContestUtils::getArticleContent( $contest->getField( 'opportunities' ) ) ); |
| 177 | + } |
| 178 | + |
| 179 | + /** |
| 180 | + * Show the rules for this contest. |
| 181 | + * |
| 182 | + * @since 0.1 |
| 183 | + * |
| 184 | + * @param Contest $contest |
| 185 | + */ |
| 186 | + protected function showRules( Contest $contest ) { |
| 187 | + $this->getOutput()->addHTML( Html::element( |
| 188 | + 'div', |
| 189 | + array( |
| 190 | + 'id' => 'contest-rules', |
| 191 | + 'data-rules' => ContestUtils::getParsedArticleContent( $contest->getField( 'rules_page' ) ) |
| 192 | + ) |
| 193 | + ) ); |
| 194 | + } |
| 195 | + |
| 196 | + /** |
| 197 | + * Gets the URL for the signup links. |
| 198 | + * When the user has to login, this will be to the login page, |
| 199 | + * with a retunrto to the signup page. |
| 200 | + * |
| 201 | + * @since 0.1 |
| 202 | + * |
| 203 | + * @param string $contestName |
| 204 | + * @param integer|false $challengeId |
| 205 | + * |
| 206 | + * @return string |
| 207 | + */ |
| 208 | + protected function getSignupLink( $contestName, $challengeId = false ) { |
| 209 | + if ( $challengeId !== false ) { |
| 210 | + $contestName .= '/' . $challengeId; |
| 211 | + } |
| 212 | + |
| 213 | + $signupTitle = SpecialPage::getTitleFor( 'ContestSignup', $contestName ); |
| 214 | + |
| 215 | + if ( $this->getUser()->isLoggedIn() ) { |
| 216 | + return $signupTitle->getLocalURL(); |
| 217 | + } |
| 218 | + else { |
| 219 | + return SpecialPage::getTitleFor( 'Userlogin' )->getLocalURL( array( |
| 220 | + //'type' => 'signup', |
| 221 | + 'returnto' => $signupTitle->getFullText(), |
| 222 | + 'campaign' => 'contests' |
| 223 | + ) ); |
| 224 | + } |
| 225 | + } |
| 226 | + |
| 227 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestWelcome.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 228 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestPage.php |
— | — | @@ -0,0 +1,184 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Base special page for special pages in the Contest extension, |
| 6 | + * taking care of some common stuff and providing compatibility helpers. |
| 7 | + * |
| 8 | + * @since 0.1 |
| 9 | + * |
| 10 | + * @file SpecialContestPage.php |
| 11 | + * @ingroup Contest |
| 12 | + * |
| 13 | + * @licence GNU GPL v3 or later |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +abstract class SpecialContestPage extends SpecialPage { |
| 17 | + |
| 18 | + public $subPage; |
| 19 | + |
| 20 | + /** |
| 21 | + * @see SpecialPage::getDescription |
| 22 | + * |
| 23 | + * @since 0.1 |
| 24 | + * @return String |
| 25 | + */ |
| 26 | + public function getDescription() { |
| 27 | + return wfMsg( 'special-' . strtolower( $this->getName() ) ); |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * Sets headers - this should be called from the execute() method of all derived classes! |
| 32 | + * |
| 33 | + * @since 0.1 |
| 34 | + */ |
| 35 | + public function setHeaders() { |
| 36 | + $out = $this->getOutput(); |
| 37 | + $out->setArticleRelated( false ); |
| 38 | + $out->setRobotPolicy( 'noindex,nofollow' ); |
| 39 | + $out->setPageTitle( $this->getDescription() ); |
| 40 | + } |
| 41 | + |
| 42 | + /** |
| 43 | + * Main method. |
| 44 | + * |
| 45 | + * @since 0.1 |
| 46 | + * |
| 47 | + * @param string $arg |
| 48 | + */ |
| 49 | + public function execute( $subPage ) { |
| 50 | + $this->subPage = $subPage; |
| 51 | + |
| 52 | + $this->setHeaders(); |
| 53 | + $this->outputHeader(); |
| 54 | + |
| 55 | + // If the user is authorized, display the page, if not, show an error. |
| 56 | + if ( !$this->userCanExecute( $this->getUser() ) ) { |
| 57 | + $this->displayRestrictionError(); |
| 58 | + return false; |
| 59 | + } |
| 60 | + |
| 61 | + return true; |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Show a message in an error box. |
| 66 | + * |
| 67 | + * @since 0.1 |
| 68 | + * |
| 69 | + * @param string $message |
| 70 | + */ |
| 71 | + protected function showError( $message ) { |
| 72 | + $this->getOutput()->addHTML( |
| 73 | + '<p class="visualClear errorbox">' . wfMsgExt( $message, 'parseinline' ) . '</p>' |
| 74 | + ); |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * Show a message in a warning box. |
| 79 | + * |
| 80 | + * @since 0.1 |
| 81 | + * |
| 82 | + * @param string $message |
| 83 | + */ |
| 84 | + protected function showWarning( $message ) { |
| 85 | + $this->getOutput()->addHTML( |
| 86 | + '<p class="visualClear warningbox">' . wfMsgExt( $message, 'parseinline' ) . '</p>' |
| 87 | + ); |
| 88 | + } |
| 89 | + |
| 90 | + /** |
| 91 | + * Show a message in a success box. |
| 92 | + * |
| 93 | + * @since 0.1 |
| 94 | + * |
| 95 | + * @param string $message |
| 96 | + */ |
| 97 | + protected function showSuccess( $message, $subst = '' ) { |
| 98 | + $this->getOutput()->addHTML( |
| 99 | + '<div class="successbox"><strong><p>' . wfMsgExt( $message, array( 'parseinline' ), $subst ) . '</p></strong></div>' |
| 100 | + ); |
| 101 | + } |
| 102 | + |
| 103 | + /** |
| 104 | + * Get an array of navigation links. |
| 105 | + * |
| 106 | + * @param string $contestName |
| 107 | + * @param User $user |
| 108 | + * @param string|false $exclude |
| 109 | + * |
| 110 | + * @since 0.1 |
| 111 | + * |
| 112 | + * @return array |
| 113 | + */ |
| 114 | + protected static function getNavigationLinks( $contestName, User $user, $exclude = false ) { |
| 115 | + $pages = array(); |
| 116 | + |
| 117 | + $pages['contest-nav-contests'] = array( 'Contests' ); |
| 118 | + |
| 119 | + if ( $user->isAllowed( 'contestjudge' ) ) { |
| 120 | + $pages['contest-nav-contest'] = array( 'Contest', $contestName ); |
| 121 | + } |
| 122 | + |
| 123 | + if ( $user->isAllowed( 'contestadmin' ) ) { |
| 124 | + $pages['contest-nav-editcontest'] = array( 'EditContest', $contestName ); |
| 125 | + } |
| 126 | + |
| 127 | + $pages['contest-nav-contestwelcome'] = array( 'ContestWelcome', $contestName ); |
| 128 | + |
| 129 | + if ( $user->isAllowed( 'contestparticipant' ) ) { |
| 130 | + $pages['contest-nav-contestsignup'] = array( 'ContestSignup', $contestName ); |
| 131 | + } |
| 132 | + |
| 133 | + $links = array(); |
| 134 | + |
| 135 | + foreach ( $pages as $message => $page ) { |
| 136 | + $page = (array)$page; |
| 137 | + |
| 138 | + if ( $exclude !== false && $page[0] == $exclude ) { |
| 139 | + continue; |
| 140 | + } |
| 141 | + |
| 142 | + $subPage = count( $page ) > 1 ? $page[1] : false; |
| 143 | + |
| 144 | + $links[] = Html::element( |
| 145 | + 'a', |
| 146 | + array( 'href' => SpecialPage::getTitleFor( $page[0], $subPage )->getLocalURL() ), |
| 147 | + wfMsgExt( $message, 'parseinline', $subPage ) |
| 148 | + ); |
| 149 | + } |
| 150 | + |
| 151 | + return $links; |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Get the navigation links for the specified contest in a pipe-separated list. |
| 156 | + * |
| 157 | + * @since 0.1 |
| 158 | + * |
| 159 | + * @param string $contestName |
| 160 | + * @param User $user |
| 161 | + * @param Language $lang |
| 162 | + * @param boolean $exclude |
| 163 | + * @return string |
| 164 | + */ |
| 165 | + public static function getNavigation( $contestName, User $user, Language $lang, $exclude = false ) { |
| 166 | + $links = self::getNavigationLinks( $contestName, $user, $exclude ); |
| 167 | + return Html::rawElement( 'p', array(), $lang->pipeList( $links ) ); |
| 168 | + } |
| 169 | + |
| 170 | + /** |
| 171 | + * Display navigation links. |
| 172 | + * |
| 173 | + * @since 0.1 |
| 174 | + * |
| 175 | + * @param string|null $subPage |
| 176 | + */ |
| 177 | + protected function displayNavigation( $subPage = null ) { |
| 178 | + if ( is_null( $subPage ) ) { |
| 179 | + $subPage = $this->subPage; |
| 180 | + } |
| 181 | + |
| 182 | + $this->getOutput()->addHTML( self::getNavigation( $subPage, $this->getUser(), $this->getLang(), $this->getName() ) ); |
| 183 | + } |
| 184 | + |
| 185 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestPage.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 186 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContest.php |
— | — | @@ -0,0 +1,156 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contest interface for judges. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialContest.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialContest extends SpecialContestPage { |
| 16 | + |
| 17 | + /** |
| 18 | + * Constructor. |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + */ |
| 22 | + public function __construct() { |
| 23 | + parent::__construct( 'Contest', 'contestjudge', false ); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Main method. |
| 28 | + * |
| 29 | + * @since 0.1 |
| 30 | + * |
| 31 | + * @param string $arg |
| 32 | + */ |
| 33 | + public function execute( $subPage ) { |
| 34 | + $subPage = str_replace( '_', ' ', $subPage ); |
| 35 | + |
| 36 | + $subPage = explode( '/', $subPage, 2 ); |
| 37 | + $challengeTitle = count( $subPage ) > 1 ? $subPage[1] : false; |
| 38 | + |
| 39 | + $subPage = $subPage[0]; |
| 40 | + |
| 41 | + if ( !parent::execute( $subPage ) ) { |
| 42 | + return; |
| 43 | + } |
| 44 | + |
| 45 | + $out = $this->getOutput(); |
| 46 | + |
| 47 | + /** |
| 48 | + * @var $contest Contest |
| 49 | + */ |
| 50 | + $contest = Contest::s()->selectRow( null, array( 'name' => $subPage ) ); |
| 51 | + |
| 52 | + if ( $contest === false ) { |
| 53 | + $out->redirect( SpecialPage::getTitleFor( 'Contests' )->getLocalURL() ); |
| 54 | + } |
| 55 | + else { |
| 56 | + $out->setPageTitle( wfMsgExt( 'contest-contest-title', 'parseinline', $contest->getField( 'name' ) ) ); |
| 57 | + $this->displayNavigation(); |
| 58 | + $this->showGeneralInfo( $contest ); |
| 59 | + $this->showContestants( $contest, $challengeTitle ); |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + /** |
| 64 | + * Display the general contest info. |
| 65 | + * |
| 66 | + * @since 0.1 |
| 67 | + * |
| 68 | + * @param Contest $contest |
| 69 | + */ |
| 70 | + protected function showGeneralInfo( Contest $contest ) { |
| 71 | + $out = $this->getOutput(); |
| 72 | + |
| 73 | + $out->addHTML( Html::openElement( 'table', array( 'class' => 'wikitable contest-summary' ) ) ); |
| 74 | + |
| 75 | + foreach ( $this->getSummaryData( $contest ) as $stat => $value ) { |
| 76 | + $out->addHTML( '<tr>' ); |
| 77 | + |
| 78 | + $out->addHTML( Html::element( |
| 79 | + 'th', |
| 80 | + array( 'class' => 'contest-summary-name' ), |
| 81 | + wfMsg( 'contest-contest-' . $stat ) |
| 82 | + ) ); |
| 83 | + |
| 84 | + $out->addHTML( Html::element( |
| 85 | + 'td', |
| 86 | + array( 'class' => 'contest-summary-value' ), |
| 87 | + $value |
| 88 | + ) ); |
| 89 | + |
| 90 | + $out->addHTML( '</tr>' ); |
| 91 | + } |
| 92 | + |
| 93 | + $out->addHTML( Html::closeElement( 'table' ) ); |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Gets the summary data. |
| 98 | + * |
| 99 | + * @since 0.1 |
| 100 | + * |
| 101 | + * @param Contest $contest |
| 102 | + * |
| 103 | + * @return array |
| 104 | + */ |
| 105 | + protected function getSummaryData( Contest $contest ) { |
| 106 | + $stats = array(); |
| 107 | + |
| 108 | + $stats['name'] = $contest->getField( 'name' ); |
| 109 | + $stats['status'] = Contest::getStatusMessage( $contest->getStatus() ); |
| 110 | + $stats['submissioncount'] = $this->getLang()->formatNum( $contest->getField( 'submission_count' ) ); |
| 111 | + |
| 112 | + return $stats; |
| 113 | + } |
| 114 | + |
| 115 | + /** |
| 116 | + * Show a paged list of the contestants foe this contest. |
| 117 | + * |
| 118 | + * @since 0.1 |
| 119 | + * |
| 120 | + * @param Contest $contest |
| 121 | + * @param string|false $challengeTitle |
| 122 | + */ |
| 123 | + protected function showContestants( Contest $contest, $challengeTitle ) { |
| 124 | + $out = $this->getOutput(); |
| 125 | + |
| 126 | + $out->addHTML( Html::element( 'h3', array(), wfMsg( 'contest-contest-contestants' ) ) ); |
| 127 | + |
| 128 | + $conds = array( |
| 129 | + 'contestant_contest_id' => $contest->getId() |
| 130 | + ); |
| 131 | + |
| 132 | + if ( $challengeTitle !== false ) { |
| 133 | + $challenge = ContestChallenge::s()->selectRow( 'id', array( 'title' => $challengeTitle ) ); |
| 134 | + |
| 135 | + if ( $challenge !== false ) { |
| 136 | + $conds['contestant_challenge_id'] = $challenge->getField( 'id' ); |
| 137 | + unset( $conds['contestant_contest_id'] ); // Not needed because the challenge implies the context |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + $out->addWikiMsg( 'contest-contest-contestants-text' ); |
| 142 | + |
| 143 | + $pager = new ContestantPager( $this, $conds ); |
| 144 | + |
| 145 | + if ( $pager->getNumRows() ) { |
| 146 | + $out->addHTML( |
| 147 | + $pager->getNavigationBar() . |
| 148 | + $pager->getBody() . |
| 149 | + $pager->getNavigationBar() |
| 150 | + ); |
| 151 | + } |
| 152 | + else { |
| 153 | + $out->addWikiMsg( 'contest-contest-no-results' ); |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContest.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 158 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestant.php |
— | — | @@ -0,0 +1,354 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contest interface for judges. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialContest.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialContestant extends SpecialContestPage { |
| 16 | + |
| 17 | + /** |
| 18 | + * Constructor. |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + */ |
| 22 | + public function __construct() { |
| 23 | + parent::__construct( 'Contestant', 'contestjudge', false ); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Main method. |
| 28 | + * |
| 29 | + * @since 0.1 |
| 30 | + * |
| 31 | + * @param string $arg |
| 32 | + */ |
| 33 | + public function execute( $subPage ) { |
| 34 | + if ( !parent::execute( $subPage ) ) { |
| 35 | + return; |
| 36 | + } |
| 37 | + |
| 38 | + $contestant = ContestContestant::s()->selectRow( 'id', array( 'id' => (int)$subPage ) ); |
| 39 | + |
| 40 | + if ( $contestant === false ) { |
| 41 | + $this->getOutput()->redirect( SpecialPage::getTitleFor( 'Contests' )->getLocalURL() ); |
| 42 | + } |
| 43 | + else { |
| 44 | + if ( $this->getRequest()->wasPosted() |
| 45 | + && $this->getUser()->matchEditToken( $this->getRequest()->getVal( 'wpEditToken' ) ) ) |
| 46 | + { |
| 47 | + $this->handleSubmission( $contestant->getId() ); |
| 48 | + } |
| 49 | + |
| 50 | + $contestant->loadFields(); |
| 51 | + $this->showPage( $contestant ); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * Handle a submission by inserting/updating the vote |
| 57 | + * and (optionally) adding the comment. |
| 58 | + * |
| 59 | + * @since 0.1 |
| 60 | + * |
| 61 | + * @param integer $contestantId |
| 62 | + * |
| 63 | + * @return boolean Success indicator |
| 64 | + */ |
| 65 | + protected function handleSubmission( $contestantId ) { |
| 66 | + $success = true; |
| 67 | + |
| 68 | + if ( trim( $this->getRequest()->getText( 'new-comment-text' ) ) !== '' ) { |
| 69 | + $comment = new ContestComment( array( |
| 70 | + 'user_id' => $this->getUser()->getId(), |
| 71 | + 'contestant_id' => $contestantId, |
| 72 | + |
| 73 | + 'text' => $this->getRequest()->getText( 'new-comment-text' ), |
| 74 | + 'time' => wfTimestampNow() |
| 75 | + ) ); |
| 76 | + |
| 77 | + $success = $comment->writeToDB(); |
| 78 | + |
| 79 | + if ( $success ) { |
| 80 | + ContestContestant::s()->addToField( 'comments', 1 ); |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + if ( $success && !is_null( $this->getRequest()->getVal( 'contestant-rating' ) ) ) { |
| 85 | + $attribs = array( |
| 86 | + 'value' => $this->getRequest()->getInt( 'contestant-rating' ), |
| 87 | + 'contestant_id' => $contestantId, |
| 88 | + 'user_id' => $this->getUser()->getId() |
| 89 | + ); |
| 90 | + |
| 91 | + if ( !is_null( $this->getRequest()->getVal( 'contestant-vote-id' ) ) ) { |
| 92 | + $attribs['id'] = $this->getRequest()->getInt( 'contestant-vote-id' ); |
| 93 | + } |
| 94 | + |
| 95 | + $vote = new ContestVote( $attribs ); |
| 96 | + $success = $vote->writeToDB() && $success; |
| 97 | + } |
| 98 | + |
| 99 | + return $success; |
| 100 | + } |
| 101 | + |
| 102 | + /** |
| 103 | + * Show the actual page, conisting of the navigation, the summary and |
| 104 | + * the rating and voting controls. |
| 105 | + * |
| 106 | + * @since 0.1 |
| 107 | + * |
| 108 | + * @param ContestContestant $contestant |
| 109 | + */ |
| 110 | + protected function showPage( ContestContestant $contestant ) { |
| 111 | + global $wgScript; |
| 112 | + $out = $this->getOutput(); |
| 113 | + |
| 114 | + $out->setPageTitle( wfMsgExt( |
| 115 | + 'contest-contestant-title', |
| 116 | + 'parseinline', |
| 117 | + $contestant->getField( 'id' ), |
| 118 | + $contestant->getContest()->getField( 'name' ) |
| 119 | + ) ); |
| 120 | + |
| 121 | + $this->displayNavigation( str_replace( ' ', '_', $contestant->getContest()->getField( 'name' ) ) ); |
| 122 | + |
| 123 | + $this->showGeneralInfo( $contestant ); |
| 124 | + |
| 125 | + $out->addHTML( '<form method="post" action="' . htmlspecialchars( $wgScript ) . '">' ); |
| 126 | + $out->addHTML( Html::hidden( 'title', $this->getTitle( $this->subPage )->getPrefixedDBkey() ) ); |
| 127 | + $out->addHTML( Html::hidden( 'wpEditToken', $this->getUser()->editToken() ) ); |
| 128 | + |
| 129 | + $this->showRating( $contestant ); |
| 130 | + $this->showComments( $contestant ); |
| 131 | + |
| 132 | + $out->addHTML( '</form>' ); |
| 133 | + |
| 134 | + $out->addModules( 'contest.special.contestant' ); |
| 135 | + } |
| 136 | + |
| 137 | + /** |
| 138 | + * Display the general contestant info. |
| 139 | + * |
| 140 | + * @since 0.1 |
| 141 | + * |
| 142 | + * @param ContestContestant $contestant |
| 143 | + */ |
| 144 | + protected function showGeneralInfo( ContestContestant $contestant ) { |
| 145 | + $out = $this->getOutput(); |
| 146 | + |
| 147 | + $out->addHTML( Html::openElement( 'table', array( 'class' => 'wikitable contestant-summary' ) ) ); |
| 148 | + |
| 149 | + foreach ( $this->getSummaryData( $contestant ) as $stat => $value ) { |
| 150 | + $out->addHTML( '<tr>' ); |
| 151 | + |
| 152 | + $out->addHTML( Html::element( |
| 153 | + 'th', |
| 154 | + array( 'class' => 'contestant-summary-name' ), |
| 155 | + wfMsg( 'contest-contestant-header-' . $stat ) |
| 156 | + ) ); |
| 157 | + |
| 158 | + $out->addHTML( Html::rawElement( |
| 159 | + 'td', |
| 160 | + array( 'class' => 'contestant-summary-value' ), |
| 161 | + $value |
| 162 | + ) ); |
| 163 | + |
| 164 | + $out->addHTML( '</tr>' ); |
| 165 | + } |
| 166 | + |
| 167 | + $out->addHTML( Html::closeElement( 'table' ) ); |
| 168 | + } |
| 169 | + |
| 170 | + /** |
| 171 | + * Gets the summary data. |
| 172 | + * Values are escaped. |
| 173 | + * |
| 174 | + * @since 0.1 |
| 175 | + * |
| 176 | + * @param ContestContestant $contestant |
| 177 | + * |
| 178 | + * @return array |
| 179 | + */ |
| 180 | + protected function getSummaryData( ContestContestant $contestant ) { |
| 181 | + $stats = array(); |
| 182 | + |
| 183 | + $stats['id'] = htmlspecialchars( $contestant->getField( 'id' ) ); |
| 184 | + $stats['contest'] = htmlspecialchars( $contestant->getContest()->getField( 'name' ) ); |
| 185 | + |
| 186 | + $challengeTitles = ContestChallenge::getTitlesForIds( $contestant->getField( 'challenge_id' ) ); |
| 187 | + $stats['challenge'] = htmlspecialchars( $challengeTitles[$contestant->getField( 'challenge_id' )] ); |
| 188 | + |
| 189 | + if ( $contestant->getField( 'submission' ) === '' ) { |
| 190 | + $stats['submission'] = htmlspecialchars( wfMsg( 'contest-contestant-notsubmitted' ) ); |
| 191 | + } |
| 192 | + else { |
| 193 | + $stats['submission'] = '<b>' . Html::element( |
| 194 | + 'a', |
| 195 | + array( 'href' => $contestant->getField( 'submission' ) ), |
| 196 | + wfMsg( 'contest-contestant-submission-url' ) |
| 197 | + ) . '</b>'; |
| 198 | + } |
| 199 | + |
| 200 | + $countries = ContestContestant::getCountries(); |
| 201 | + $stats['country'] = htmlspecialchars( $countries[$contestant->getField( 'country' )] ); |
| 202 | + |
| 203 | + $stats['wmf'] = htmlspecialchars( wfMsg( 'contest-contestant-' . ( $contestant->getField( 'wmf' ) ? 'yes' : 'no' ) ) ); |
| 204 | + $stats['volunteer'] = htmlspecialchars( wfMsg( 'contest-contestant-' . ( $contestant->getField( 'volunteer' ) ? 'yes' : 'no' ) ) ); |
| 205 | + |
| 206 | + $stats['rating'] = htmlspecialchars( wfMsgExt( |
| 207 | + 'contest-contestant-rating', |
| 208 | + 'parsemag', |
| 209 | + $this->getLang()->formatNum( $contestant->getField( 'rating' ) ), |
| 210 | + $this->getLang()->formatNum( $contestant->getField( 'rating_count' ) ) |
| 211 | + ) ); |
| 212 | + |
| 213 | + $stats['comments'] = htmlspecialchars( $this->getLang()->formatNum( $contestant->getField( 'comments' ) ) ); |
| 214 | + |
| 215 | + return $stats; |
| 216 | + } |
| 217 | + |
| 218 | + /** |
| 219 | + * Display the current rating the judge gave if any and a control to |
| 220 | + * (re)-rate. |
| 221 | + * |
| 222 | + * @since 0.1 |
| 223 | + * |
| 224 | + * @param ContestContestant $contestant |
| 225 | + */ |
| 226 | + protected function showRating( ContestContestant $contestant ) { |
| 227 | + $out = $this->getOutput(); |
| 228 | + |
| 229 | + $out->addHTML( Html::element( 'h2', array(), wfMsg( 'contest-contestant-rate' ) ) ); |
| 230 | + |
| 231 | + $vote = ContestVote::s()->selectRow( |
| 232 | + array( 'value', 'id' ), |
| 233 | + array( 'user_id' => $this->getUser()->getId(), 'contestant_id' => $contestant->getId() ) |
| 234 | + ); |
| 235 | + |
| 236 | + if ( $vote === false ) { |
| 237 | + $message = wfMsg( 'contest-contestant-not-voted' ); |
| 238 | + } |
| 239 | + else { |
| 240 | + $message = wfMsgExt( |
| 241 | + 'contest-contestant-voted', |
| 242 | + 'parsemag', |
| 243 | + $this->getLang()->formatNum( $vote->getField( 'value' ) ) |
| 244 | + ); |
| 245 | + |
| 246 | + $out->addHTML( Html::hidden( 'contestant-vote-id', $vote->getId() ) ); |
| 247 | + } |
| 248 | + |
| 249 | + $out->addHTML( Html::element( 'p', array(), $message ) ); |
| 250 | + |
| 251 | + foreach ( ContestSettings::get( 'voteValues' ) as $value ) { |
| 252 | + $attribs = array( |
| 253 | + 'type' => 'radio', |
| 254 | + 'value' => $value, |
| 255 | + 'name' => 'contestant-rating', |
| 256 | + 'id' => 'contestant-rating-' . $value |
| 257 | + ); |
| 258 | + |
| 259 | + if ( $vote !== false && $value == $vote->getField( 'value' ) ) { |
| 260 | + $attribs['checked'] = 'checked'; |
| 261 | + } |
| 262 | + |
| 263 | + $out->addHTML( |
| 264 | + Html::element( |
| 265 | + 'input', |
| 266 | + $attribs |
| 267 | + ) . |
| 268 | + Html::element( |
| 269 | + 'label', |
| 270 | + array( 'for' => 'contestant-rating-' . $value ), |
| 271 | + $this->getLang()->formatNum( $value ) |
| 272 | + ) |
| 273 | + ); |
| 274 | + } |
| 275 | + |
| 276 | + } |
| 277 | + |
| 278 | + /** |
| 279 | + * Show the comments and a control to add additional ones. |
| 280 | + * |
| 281 | + * @since 0.1 |
| 282 | + * |
| 283 | + * @param ContestContestant $contestant |
| 284 | + */ |
| 285 | + protected function showComments( ContestContestant $contestant ) { |
| 286 | + $out = $this->getOutput(); |
| 287 | + |
| 288 | + $out->addHTML( Html::element( 'h2', array(), wfMsg( 'contest-contestant-comments' ) ) ); |
| 289 | + |
| 290 | + $out->addHTML( '<div class="contestant-comments">' ); |
| 291 | + |
| 292 | + foreach ( $contestant->getComments() as /* ContestComment */ $comment ) { |
| 293 | + $out->addHTML( $this->getCommentHTML( $comment ) ); |
| 294 | + } |
| 295 | + |
| 296 | + $out->addHTML( '</div>' ); |
| 297 | + |
| 298 | + $out->addHTML( |
| 299 | + '<div class="contestant-new-comment"> |
| 300 | + <textarea cols="40" rows="10" name="new-comment-text"></textarea> |
| 301 | + </div>' |
| 302 | + ); |
| 303 | + |
| 304 | + $out->addHTML( Html::input( 'submitChanges', wfMsg( 'contest-contestant-submit' ), 'submit' ) ); |
| 305 | + } |
| 306 | + |
| 307 | + /** |
| 308 | + * Get the HTML for a single comment. |
| 309 | + * |
| 310 | + * @since 0.1 |
| 311 | + * |
| 312 | + * @param ContestComment $comment |
| 313 | + * |
| 314 | + * @return string |
| 315 | + */ |
| 316 | + protected function getCommentHTML( ContestComment $comment ) { |
| 317 | + $user = User::newFromId( $comment->getField( 'user_id' ) ); |
| 318 | + |
| 319 | + $htmlId = 'c' . $comment->getId(); |
| 320 | + |
| 321 | + $html = Html::rawElement( |
| 322 | + 'div', |
| 323 | + array( 'class' => 'contestant-comment-meta' ), |
| 324 | + Html::element( |
| 325 | + 'a', |
| 326 | + array( |
| 327 | + 'href' => $this->getTitle( $this->subPage )->getLocalURL() . "#$htmlId", |
| 328 | + 'title' => wfMsg( 'contest-contestant-permalink' ) |
| 329 | + ), |
| 330 | + '#' |
| 331 | + ) . |
| 332 | + wfMsgHtml( |
| 333 | + 'contest-contestant-comment-by', |
| 334 | + Linker::userLink( $comment->getField( 'user_id' ), $user->getName() ) . |
| 335 | + Linker::userToolLinks( $comment->getField( 'user_id' ), $user->getName() ) |
| 336 | + ) . '   ' . htmlspecialchars( $this->getLang()->timeanddate( $comment->getField( 'time' ), true ) ) |
| 337 | + ); |
| 338 | + |
| 339 | + $html .= Html::rawElement( |
| 340 | + 'div', |
| 341 | + array( 'class' => 'contestant-comment-text mw-content-' . $this->getLang()->getDir() . '' ), |
| 342 | + $this->getOutput()->parse( $comment->getField( 'text' ) ) |
| 343 | + ); |
| 344 | + |
| 345 | + return Html::rawElement( |
| 346 | + 'div', |
| 347 | + array( |
| 348 | + 'class' => 'contestant-comment', |
| 349 | + 'id' => $htmlId |
| 350 | + ), |
| 351 | + $html |
| 352 | + ); |
| 353 | + } |
| 354 | + |
| 355 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestant.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 356 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContests.php |
— | — | @@ -0,0 +1,265 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * List of contests, with admin and judge links depending on user rights. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialContests.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialContests extends SpecialContestPage { |
| 16 | + |
| 17 | + /** |
| 18 | + * Constructor. |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + */ |
| 22 | + public function __construct() { |
| 23 | + parent::__construct( 'Contests' ); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Returns if the user can access the page or not. |
| 28 | + * |
| 29 | + * @return boolean |
| 30 | + */ |
| 31 | + protected function userCanAccess() { |
| 32 | + $user = $this->getUser(); |
| 33 | + return |
| 34 | + ( $user->isAllowed( 'contestadmin' ) || $user->isAllowed( 'contestjudge' ) ) |
| 35 | + && !$user->isBlocked(); |
| 36 | + } |
| 37 | + |
| 38 | + /** |
| 39 | + * This page is unlisted because the only way to access it is though a contest |
| 40 | + * landing page. |
| 41 | + * |
| 42 | + * @return false|boolean |
| 43 | + */ |
| 44 | + public function isListed() { |
| 45 | + return $this->userCanAccess(); |
| 46 | + } |
| 47 | + |
| 48 | + /** |
| 49 | + * Main method. |
| 50 | + * |
| 51 | + * @since 0.1 |
| 52 | + * |
| 53 | + * @param string $arg |
| 54 | + */ |
| 55 | + public function execute( $subPage ) { |
| 56 | + $subPage = str_replace( '_', ' ', $subPage ); |
| 57 | + |
| 58 | + if ( !parent::execute( $subPage ) ) { |
| 59 | + return; |
| 60 | + } |
| 61 | + |
| 62 | + $user = $this->getUser(); |
| 63 | + |
| 64 | + if ( !$this->userCanAccess() ) { |
| 65 | + $this->displayRestrictionError(); |
| 66 | + } |
| 67 | + |
| 68 | + if ( $user->isAllowed( 'contestadmin' ) ) { |
| 69 | + $this->displayAddNewControl(); |
| 70 | + } |
| 71 | + |
| 72 | + $this->displayContests(); |
| 73 | + } |
| 74 | + |
| 75 | + /** |
| 76 | + * Displays the contests. |
| 77 | + * |
| 78 | + * @since 0.1 |
| 79 | + */ |
| 80 | + protected function displayContests() { |
| 81 | + $contests = Contest::s()->select( array( 'id', 'name', 'status', 'end', 'submission_count' ) ); |
| 82 | + |
| 83 | + if ( count( $contests ) > 0 ) { |
| 84 | + $this->displayContestsTable( $contests ); |
| 85 | + } |
| 86 | + |
| 87 | + $this->getOutput()->addModules( 'ext.contest.special.contests' ); |
| 88 | + } |
| 89 | + |
| 90 | + /** |
| 91 | + * Displays a small form to add a new campaign. |
| 92 | + * |
| 93 | + * @since 0.1 |
| 94 | + */ |
| 95 | + protected function displayAddNewControl() { |
| 96 | + $out = $this->getOutput(); |
| 97 | + |
| 98 | + $out->addHTML( Html::openElement( |
| 99 | + 'form', |
| 100 | + array( |
| 101 | + 'method' => 'post', |
| 102 | + 'action' => SpecialPage::getTitleFor( 'EditContest' )->getLocalURL(), |
| 103 | + ) |
| 104 | + ) ); |
| 105 | + |
| 106 | + $out->addHTML( '<fieldset>' ); |
| 107 | + |
| 108 | + $out->addHTML( '<legend>' . htmlspecialchars( wfMsg( 'contest-special-addnew' ) ) . '</legend>' ); |
| 109 | + |
| 110 | + $out->addHTML( Html::element( 'p', array(), wfMsg( 'contest-special-namedoc' ) ) ); |
| 111 | + |
| 112 | + $out->addHTML( Html::element( 'label', array( 'for' => 'newcontest' ), wfMsg( 'contest-special-newname' ) ) ); |
| 113 | + |
| 114 | + $out->addHTML( ' ' . Html::input( 'newcontest' ) . ' ' ); |
| 115 | + |
| 116 | + $out->addHTML( Html::input( |
| 117 | + 'addnewcontest', |
| 118 | + wfMsg( 'contest-special-add' ), |
| 119 | + 'submit' |
| 120 | + ) ); |
| 121 | + |
| 122 | + $out->addHTML( Html::hidden( 'newEditToken', $this->getUser()->editToken() ) ); |
| 123 | + |
| 124 | + $out->addHTML( '</fieldset></form>' ); |
| 125 | + } |
| 126 | + |
| 127 | + /** |
| 128 | + * Displays a list of all contests. |
| 129 | + * |
| 130 | + * @since 0.1 |
| 131 | + * |
| 132 | + * @param array $contests |
| 133 | + */ |
| 134 | + protected function displayContestsTable( array /* of Contest */ $contests ) { |
| 135 | + $user = $this->getUser(); |
| 136 | + $out = $this->getOutput(); |
| 137 | + |
| 138 | + $out->addHTML( Html::element( 'h2', array( 'class' => 'contests-title' ), wfMsg( 'contest-special-existing' ) ) ); |
| 139 | + |
| 140 | + $out->addHTML( Xml::openElement( |
| 141 | + 'table', |
| 142 | + array( 'class' => 'wikitable sortable contests-table' ) |
| 143 | + ) ); |
| 144 | + |
| 145 | + $headers = array( |
| 146 | + Html::element( 'th', array(), wfMsg( 'contest-special-name' ) ), |
| 147 | + Html::element( 'th', array(), wfMsg( 'contest-special-status' ) ), |
| 148 | + Html::element( 'th', array(), wfMsg( 'contest-special-submissioncount' ) ) |
| 149 | + ); |
| 150 | + |
| 151 | + $headers[] = Html::element( 'th', array( 'class' => 'unsortable' ) ); |
| 152 | + |
| 153 | +// if ( $user->isAllowed( 'contestadmin' ) ) { |
| 154 | +// $headers[] = Html::element( 'th', array( 'class' => 'unsortable' ), wfMsg( 'contest-special-edit' ) ); |
| 155 | +// $headers[] = Html::element( 'th', array( 'class' => 'unsortable' ), wfMsg( 'contest-special-delete' ) ); |
| 156 | +// } |
| 157 | + |
| 158 | + $out->addHTML( '<thead><tr>' . implode( '', $headers ) . '</tr></thead>' ); |
| 159 | + |
| 160 | + $out->addHTML( '<tbody>' ); |
| 161 | + |
| 162 | + foreach ( $contests as $contest ) { |
| 163 | + /** |
| 164 | + * @var $contest Contest |
| 165 | + */ |
| 166 | + |
| 167 | + $fields = array(); |
| 168 | + |
| 169 | + if ( $user->isAllowed( 'contestparticipant' ) ) { |
| 170 | + $name = Html::element( |
| 171 | + 'a', |
| 172 | + array( |
| 173 | + 'href' => SpecialPage::getTitleFor( 'ContestWelcome', $contest->getField( 'name' ) )->getLocalURL() |
| 174 | + ), |
| 175 | + $contest->getField( 'name' ) |
| 176 | + ); |
| 177 | + } |
| 178 | + else { |
| 179 | + $name = $contest->getField( 'name' ); |
| 180 | + } |
| 181 | + |
| 182 | + $fields[] = Html::rawElement( |
| 183 | + 'td', |
| 184 | + array( 'data-sort-value' => $contest->getField( 'name' ) ), |
| 185 | + $name |
| 186 | + ); |
| 187 | + |
| 188 | + $fields[] = Html::element( |
| 189 | + 'td', |
| 190 | + array( 'data-sort-value' => $contest->getStatus() ), |
| 191 | + Contest::getStatusMessage( $contest->getStatus() ) |
| 192 | + ); |
| 193 | + |
| 194 | + $fields[] = Html::element( |
| 195 | + 'td', |
| 196 | + array(), |
| 197 | + $this->getLang()->formatNum( $contest->getField( 'submission_count' ) ) |
| 198 | + ); |
| 199 | + |
| 200 | + $links = array(); |
| 201 | + |
| 202 | + if ( $user->isAllowed( 'contestjudge' ) ) { |
| 203 | + $links[] = Html::element( |
| 204 | + 'a', |
| 205 | + array( |
| 206 | + 'href' => SpecialPage::getTitleFor( 'Contest', $contest->getField( 'name' ) )->getLocalURL() |
| 207 | + ), |
| 208 | + wfMsg( 'contest-nav-contest' ) |
| 209 | + ); |
| 210 | + } |
| 211 | + |
| 212 | + if ( $user->isAllowed( 'contestadmin' ) ) { |
| 213 | + $links[] = Html::element( |
| 214 | + 'a', |
| 215 | + array( |
| 216 | + 'href' => SpecialPage::getTitleFor( 'EditContest', $contest->getField( 'name' ) )->getLocalURL() |
| 217 | + ), |
| 218 | + wfMsg( 'contest-special-edit' ) |
| 219 | + ); |
| 220 | + |
| 221 | + $links[] = Html::element( |
| 222 | + 'a', |
| 223 | + array( |
| 224 | + 'href' => '#', |
| 225 | + 'class' => 'contest-delete', |
| 226 | + 'data-contest-id' => $contest->getId(), |
| 227 | + 'data-contest-token' => $this->getUser()->editToken( 'deletecontest' . $contest->getId() ) |
| 228 | + ), |
| 229 | + wfMsg( 'contest-special-delete' ) |
| 230 | + ); |
| 231 | + } |
| 232 | + |
| 233 | + $links[] = Html::element( |
| 234 | + 'a', |
| 235 | + array( |
| 236 | + 'href' => SpecialPage::getTitleFor( 'ContestWelcome', $contest->getField( 'name' ) )->getLocalURL() |
| 237 | + ), |
| 238 | + wfMsg( 'contest-nav-contestwelcome' ) |
| 239 | + ); |
| 240 | + |
| 241 | + if ( $user->isAllowed( 'contestparticipant' ) ) { |
| 242 | + $links[] = Html::element( |
| 243 | + 'a', |
| 244 | + array( |
| 245 | + 'href' => SpecialPage::getTitleFor( 'ContestSignup', $contest->getField( 'name' ) )->getLocalURL() |
| 246 | + ), |
| 247 | + wfMsg( 'contest-nav-contestsignup' ) |
| 248 | + ); |
| 249 | + } |
| 250 | + |
| 251 | + $fields[] = Html::rawElement( |
| 252 | + 'td', |
| 253 | + array(), |
| 254 | + $this->getLang()->pipeList( $links ) |
| 255 | + ); |
| 256 | + |
| 257 | + $out->addHTML( '<tr>' . implode( '', $fields ) . '</tr>' ); |
| 258 | + } |
| 259 | + |
| 260 | + $out->addHTML( '</tbody>' ); |
| 261 | + $out->addHTML( '</table>' ); |
| 262 | + |
| 263 | + $out->addModules( 'contest.special.contests' ); |
| 264 | + } |
| 265 | + |
| 266 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContests.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 267 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestSignup.php |
— | — | @@ -0,0 +1,394 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contest signup interface for participants. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialContestSignup.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialContestSignup extends SpecialContestPage { |
| 16 | + |
| 17 | + /** |
| 18 | + * Constructor. |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + */ |
| 22 | + public function __construct() { |
| 23 | + parent::__construct( 'ContestSignup' ); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Main method. |
| 28 | + * |
| 29 | + * @since 0.1 |
| 30 | + * |
| 31 | + * @param string $arg |
| 32 | + */ |
| 33 | + public function execute( $subPage ) { |
| 34 | + $subPage = str_replace( '_', ' ', $subPage ); |
| 35 | + |
| 36 | + if ( !parent::execute( $subPage ) ) { |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + if ( $this->getRequest()->wasPosted() && $this->getUser()->matchEditToken( $this->getRequest()->getVal( 'wpEditToken' ) ) ) { |
| 41 | + $this->showSignupForm( Contest::s()->selectRow( null, array( 'id' => $this->getRequest()->getInt( 'wpcontest-id' ) ) ) ); |
| 42 | + } |
| 43 | + else { |
| 44 | + $this->showPage( $subPage ); |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + /** |
| 49 | + * This page is unlisted because the only way to access it is though a contest |
| 50 | + * landing page. |
| 51 | + * |
| 52 | + * @return false|boolean |
| 53 | + */ |
| 54 | + public function isListed() { |
| 55 | + return false; |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * Handle form submission. |
| 60 | + * |
| 61 | + * @since 0.1 |
| 62 | + * |
| 63 | + * @return true|array |
| 64 | + */ |
| 65 | + public function handleSubmission( array $data ) { |
| 66 | + $user = $this->getUser(); |
| 67 | + |
| 68 | + $user->setEmail( $data['contestant-email'] ); |
| 69 | + $user->setRealName( $data['contestant-realname'] ); |
| 70 | + $user->saveSettings(); |
| 71 | + |
| 72 | + $contestant = new ContestContestant( array( |
| 73 | + 'contest_id' => $data['contest-id'], |
| 74 | + 'user_id' => $user->getId(), |
| 75 | + 'challenge_id' => $data['contestant-challengeid'], |
| 76 | + |
| 77 | + 'full_name' => $data['contestant-realname'], |
| 78 | + 'user_name' => $user->getName(), |
| 79 | + 'email' => $data['contestant-email'], |
| 80 | + |
| 81 | + 'country' => $data['contestant-country'], |
| 82 | + 'volunteer' => $data['contestant-volunteer'], |
| 83 | + 'wmf' => $data['contestant-wmf'], |
| 84 | + ) ); |
| 85 | + |
| 86 | + return $contestant->writeToDB(); |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Show the page. |
| 91 | + * |
| 92 | + * @since 0.1 |
| 93 | + * |
| 94 | + * @param string $subPage |
| 95 | + */ |
| 96 | + protected function showPage( $subPage ) { |
| 97 | + $out = $this->getOutput(); |
| 98 | + |
| 99 | + $subPage = explode( '/', $subPage ); |
| 100 | + $contestName = $subPage[0]; |
| 101 | + $challengeId = count( $subPage ) > 1 ? $subPage[1] : false; |
| 102 | + |
| 103 | + $contest = Contest::s()->selectRow( null, array( 'name' => $contestName ) ); |
| 104 | + |
| 105 | + if ( $contest === false ) { |
| 106 | + $this->showError( 'contest-signup-unknown' ); |
| 107 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 108 | + $out->returnToMain(); |
| 109 | + return; |
| 110 | + } |
| 111 | + switch ( $contest->getStatus() ) { |
| 112 | + case Contest::STATUS_ACTIVE: |
| 113 | + $this->showEnabledPage( $contest, $challengeId ); |
| 114 | + break; |
| 115 | + case Contest::STATUS_DRAFT: |
| 116 | + $this->showWarning( 'contest-signup-draft' ); |
| 117 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 118 | + $out->returnToMain(); |
| 119 | + break; |
| 120 | + case Contest::STATUS_FINISHED: |
| 121 | + case Contest::STATUS_EXPIRED: |
| 122 | + $this->showWarning( 'contest-signup-finished' ); |
| 123 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 124 | + $out->returnToMain(); |
| 125 | + break; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + /** |
| 130 | + * Handle page request when the contest is enabled. |
| 131 | + * |
| 132 | + * @since 0.1 |
| 133 | + * |
| 134 | + * @param Contest $contest |
| 135 | + * @param integer|false $challengeId |
| 136 | + */ |
| 137 | + protected function showEnabledPage( Contest $contest, $challengeId ) { |
| 138 | + $out = $this->getOutput(); |
| 139 | + |
| 140 | + // Check if the user is already a contestant in this contest. |
| 141 | + // If he is, reirect to submission page, else show signup form. |
| 142 | + $contestant = ContestContestant::s()->selectRow( |
| 143 | + 'id', |
| 144 | + array( |
| 145 | + 'contest_id' => $contest->getId(), |
| 146 | + 'user_id' => $this->getUser()->getId() |
| 147 | + ) |
| 148 | + ); |
| 149 | + |
| 150 | + if ( $contestant === false ) { |
| 151 | + $out->setPageTitle( $contest->getField( 'name' ) ); |
| 152 | + $out->addWikiMsg( 'contest-signup-header', $contest->getField( 'name' ) ); |
| 153 | + |
| 154 | + $this->showSignupForm( $contest, $challengeId ); |
| 155 | + } |
| 156 | + else { |
| 157 | + $out->redirect( SpecialPage::getTitleFor( 'MyContests', $contest->getField( 'name' ) )->getLocalURL() ); |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Display the signup form for this contest. |
| 163 | + * |
| 164 | + * @since 0.1 |
| 165 | + * |
| 166 | + * @param Contest $contest |
| 167 | + * @param integer|false $challengeId |
| 168 | + */ |
| 169 | + protected function showSignupForm( Contest $contest, $challengeId = false ) { |
| 170 | + $form = new HTMLForm( $this->getFormFields( $contest, $challengeId ), $this->getContext() ); |
| 171 | + |
| 172 | + $form->setSubmitCallback( array( $this, 'handleSubmission' ) ); |
| 173 | + $form->setSubmitText( wfMsg( 'contest-signup-submit' ) ); |
| 174 | + |
| 175 | + if( $form->show() ) { |
| 176 | + $this->showSucess( $contest ); |
| 177 | + } |
| 178 | + else { |
| 179 | + $this->getOutput()->addModules( 'contest.special.signup' ); |
| 180 | + } |
| 181 | + |
| 182 | + $this->getOutput()->addScript( |
| 183 | + Skin::makeVariablesScript( |
| 184 | + array( |
| 185 | + 'ContestConfig' => array( 'rules_page' => ContestUtils::getParsedArticleContent( $contest->getField( 'rules_page' ) ) ) |
| 186 | + ) |
| 187 | + ) |
| 188 | + ); |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Redirect the user to the contest page and add the "new" argument to the URL |
| 193 | + * so they get a success message. |
| 194 | + * |
| 195 | + * @since 0.1 |
| 196 | + * |
| 197 | + * @param Contest $contest |
| 198 | + */ |
| 199 | + protected function showSucess( Contest $contest ) { |
| 200 | + $url = SpecialPage::getTitleFor( 'MyContests', $contest->getField( 'name' ) )->getLocalURL( 'new' ); |
| 201 | + $this->getOutput()->redirect( $url ); |
| 202 | + } |
| 203 | + |
| 204 | + /** |
| 205 | + * Gets the field definitions for the form. |
| 206 | + * |
| 207 | + * @since 0.1 |
| 208 | + * |
| 209 | + * @param Contest $contest |
| 210 | + * @param integer|false $challengeId |
| 211 | + * @return array |
| 212 | + */ |
| 213 | + protected function getFormFields( Contest $contest, $challengeId ) { |
| 214 | + $fields = array(); |
| 215 | + |
| 216 | + $user = $this->getUser(); |
| 217 | + |
| 218 | + $fields['contest-id'] = array( |
| 219 | + 'type' => 'hidden', |
| 220 | + 'default' => $contest->getId(), |
| 221 | + 'id' => 'contest-id', |
| 222 | + ); |
| 223 | + |
| 224 | + $fields['contestant-realname'] = array( |
| 225 | + 'type' => 'text', |
| 226 | + 'default' => $user->getRealName(), |
| 227 | + 'label-message' => 'contest-signup-realname', |
| 228 | + 'required' => true, |
| 229 | + 'validation-callback' => array( __CLASS__, 'validateNameField' ) |
| 230 | + ); |
| 231 | + |
| 232 | + $fields['contestant-email'] = array( |
| 233 | + 'type' => 'text', |
| 234 | + 'default' => $user->getEmail(), |
| 235 | + 'label-message' => 'contest-signup-email', |
| 236 | + 'required' => true, |
| 237 | + 'validation-callback' => array( __CLASS__, 'validateEmailField' ) |
| 238 | + ); |
| 239 | + |
| 240 | + $fields['contestant-country'] = array( |
| 241 | + 'type' => 'select', |
| 242 | + 'label-message' => 'contest-signup-country', |
| 243 | + 'required' => true, |
| 244 | + 'options' => ContestContestant::getCountriesForInput( true ), |
| 245 | + 'validation-callback' => array( __CLASS__, 'validateCountryField' ) |
| 246 | + ); |
| 247 | + |
| 248 | + $fields['contestant-challengeid'] = array( |
| 249 | + 'type' => 'radio', |
| 250 | + 'label-message' => 'contest-signup-challenge', |
| 251 | + 'options' => $this->getChallengesList( $contest ), |
| 252 | + 'required' => true, |
| 253 | + 'validation-callback' => array( __CLASS__, 'validateChallengeField' ) |
| 254 | + ); |
| 255 | + |
| 256 | + if ( $challengeId !== false ) { |
| 257 | + $fields['contestant-challengeid']['default'] = $challengeId; |
| 258 | + } |
| 259 | + |
| 260 | + $fields['contestant-volunteer'] = array( |
| 261 | + 'type' => 'check', |
| 262 | + 'default' => '0', |
| 263 | + 'label-message' => 'contest-signup-volunteer', |
| 264 | + ); |
| 265 | + |
| 266 | + $fields['contestant-wmf'] = array( |
| 267 | + 'type' => 'check', |
| 268 | + 'default' => '0', |
| 269 | + 'label-message' => 'contest-signup-wmf', |
| 270 | + ); |
| 271 | + |
| 272 | + $fields['contestant-readrules'] = array( |
| 273 | + 'type' => 'check', |
| 274 | + 'default' => '0', |
| 275 | + 'label-message' => array( 'contest-signup-readrules', $contest->getField( 'rules_page' ) ), |
| 276 | + 'validation-callback' => array( __CLASS__, 'validateRulesField' ), |
| 277 | + 'id' => 'contest-rules', |
| 278 | + 'data-foo' => 'bar' |
| 279 | + ); |
| 280 | + |
| 281 | + return $fields; |
| 282 | + } |
| 283 | + |
| 284 | + /** |
| 285 | + * Gets a list of contests that can be fed directly to the options field of |
| 286 | + * an HTMLForm radio input. |
| 287 | + * challenge title => challenge id |
| 288 | + * |
| 289 | + * @since 0.1 |
| 290 | + * |
| 291 | + * @param Contest $contest |
| 292 | + * |
| 293 | + * @return array |
| 294 | + */ |
| 295 | + protected function getChallengesList( Contest $contest ) { |
| 296 | + $list = array(); |
| 297 | + |
| 298 | + foreach ( $contest->getChallenges() as /* ContestChallenge */ $challenge ) { |
| 299 | + $list[$challenge->getField( 'title' )] = $challenge->getId(); |
| 300 | + } |
| 301 | + |
| 302 | + return $list; |
| 303 | + } |
| 304 | + |
| 305 | + /** |
| 306 | + * HTMLForm field validation-callback for name field. |
| 307 | + * 1 |
| 308 | + * @since 0.1 |
| 309 | + * |
| 310 | + * @param $value String |
| 311 | + * @param $alldata Array |
| 312 | + * |
| 313 | + * @return true|string |
| 314 | + */ |
| 315 | + public static function validateNameField( $value, $alldata = null ) { |
| 316 | + if ( strlen( $value ) < 2 ) { |
| 317 | + return wfMsg( 'contest-signup-invalid-name' ); |
| 318 | + } |
| 319 | + |
| 320 | + return true; |
| 321 | + } |
| 322 | + |
| 323 | + /** |
| 324 | + * HTMLForm field validation-callback for email field. |
| 325 | + * |
| 326 | + * @since 0.1 |
| 327 | + * |
| 328 | + * @param $value String |
| 329 | + * @param $alldata Array |
| 330 | + * |
| 331 | + * @return true|string |
| 332 | + */ |
| 333 | + public static function validateEmailField( $value, $alldata = null ) { |
| 334 | + if ( !Sanitizer::validateEmail( $value ) ) { |
| 335 | + return wfMsg( 'contest-signup-invalid-email' ); |
| 336 | + } |
| 337 | + |
| 338 | + return true; |
| 339 | + } |
| 340 | + |
| 341 | + /** |
| 342 | + * HTMLForm field validation-callback for country field. |
| 343 | + * |
| 344 | + * @since 0.1 |
| 345 | + * |
| 346 | + * @param $value String |
| 347 | + * @param $alldata Array |
| 348 | + * |
| 349 | + * @return true|string |
| 350 | + */ |
| 351 | + public static function validateCountryField( $value, $alldata = null ) { |
| 352 | + if ( $value === '' ) { |
| 353 | + return wfMsg( 'contest-signup-require-country' ); |
| 354 | + } |
| 355 | + |
| 356 | + return true; |
| 357 | + } |
| 358 | + |
| 359 | + /** |
| 360 | + * HTMLForm field validation-callback for rules field. |
| 361 | + * |
| 362 | + * @since 0.1 |
| 363 | + * |
| 364 | + * @param $value String |
| 365 | + * @param $alldata Array |
| 366 | + * |
| 367 | + * @return true|string |
| 368 | + */ |
| 369 | + public static function validateRulesField( $value, $alldata = null ) { |
| 370 | + if ( !$value ) { |
| 371 | + return wfMsg( 'contest-signup-require-rules' ); |
| 372 | + } |
| 373 | + |
| 374 | + return true; |
| 375 | + } |
| 376 | + |
| 377 | + /** |
| 378 | + * HTMLForm field validation-callback for challenge field. |
| 379 | + * |
| 380 | + * @since 0.1 |
| 381 | + * |
| 382 | + * @param $value String |
| 383 | + * @param $alldata Array |
| 384 | + * |
| 385 | + * @return true|string |
| 386 | + */ |
| 387 | + public static function validateChallengeField( $value, $alldata = null ) { |
| 388 | + if ( is_null( $value ) ) { |
| 389 | + return wfMsg( 'contest-signup-require-challenge' ); |
| 390 | + } |
| 391 | + |
| 392 | + return true; |
| 393 | + } |
| 394 | + |
| 395 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialContestSignup.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 396 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialEditContest.php |
— | — | @@ -0,0 +1,402 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contest editing interface for contest admins. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialEditContest.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialEditContest extends FormSpecialPage { |
| 16 | + |
| 17 | + protected $contest = false; |
| 18 | + protected $isNewPost = false; |
| 19 | + |
| 20 | + /** |
| 21 | + * Constructor. |
| 22 | + * |
| 23 | + * @since 0.1 |
| 24 | + */ |
| 25 | + public function __construct() { |
| 26 | + parent::__construct( 'EditContest', 'contestadmin', false ); |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * @see SpecialPage::getDescription |
| 31 | + * |
| 32 | + * @since 0.1 |
| 33 | + */ |
| 34 | + public function getDescription() { |
| 35 | + return wfMsg( 'special-' . strtolower( $this->getName() ) ); |
| 36 | + } |
| 37 | + |
| 38 | + /** |
| 39 | + * Sets headers - this should be called from the execute() method of all derived classes! |
| 40 | + * |
| 41 | + * @since 0.1 |
| 42 | + */ |
| 43 | + public function setHeaders() { |
| 44 | + $out = $this->getOutput(); |
| 45 | + $out->setArticleRelated( false ); |
| 46 | + $out->setRobotPolicy( 'noindex,nofollow' ); |
| 47 | + $out->setPageTitle( $this->getDescription() ); |
| 48 | + } |
| 49 | + |
| 50 | + /** |
| 51 | + * Main method. |
| 52 | + * |
| 53 | + * @since 0.1 |
| 54 | + * |
| 55 | + * @param string $subPage |
| 56 | + */ |
| 57 | + public function execute( $subPage ) { |
| 58 | + $subPage = str_replace( '_', ' ', $subPage ); |
| 59 | + |
| 60 | + $this->setParameter( $subPage ); |
| 61 | + $this->setHeaders(); |
| 62 | + $this->outputHeader(); |
| 63 | + |
| 64 | + // This will throw exceptions if there's a problem |
| 65 | + $this->userCanExecute( $this->getUser() ); |
| 66 | + |
| 67 | + if ( $this->getRequest()->wasPosted() && $this->getUser()->matchEditToken( $this->getRequest()->getVal( 'wpEditToken' ) ) ) { |
| 68 | + $this->showForm(); |
| 69 | + } |
| 70 | + else { |
| 71 | + $this->showContent( $subPage ); |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + /** |
| 76 | + * Show the form. |
| 77 | + * |
| 78 | + * @since 0.1 |
| 79 | + */ |
| 80 | + protected function showForm() { |
| 81 | + $form = $this->getForm(); |
| 82 | + |
| 83 | + if ( $form->show() ) { |
| 84 | + $this->onSuccess(); |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + /** |
| 89 | + * Attempt to get the contest to be edited or create the one to be added. |
| 90 | + * If this works, show the form, if not, redirect to special:contests. |
| 91 | + * |
| 92 | + * @since 0.1 |
| 93 | + * |
| 94 | + * @param string $subPage |
| 95 | + */ |
| 96 | + protected function showContent( $subPage ) { |
| 97 | + $isNew = $this->getRequest()->wasPosted() && $this->getUser()->matchEditToken( $this->getRequest()->getVal( 'newEditToken' ) ); |
| 98 | + |
| 99 | + if ( $isNew ) { |
| 100 | + $data = array( 'name' => $this->getRequest()->getVal( 'newcontest' ) ); |
| 101 | + |
| 102 | + $contest = Contest::s()->selectRow( null, $data ); |
| 103 | + |
| 104 | + if ( $contest === false ) { |
| 105 | + $contest = new Contest( $data, true ); |
| 106 | + } |
| 107 | + else { |
| 108 | + $this->showWarning( 'contest-edit-exists-already' ); |
| 109 | + } |
| 110 | + } |
| 111 | + else { |
| 112 | + $contest = Contest::s()->selectRow( null, array( 'name' => $subPage ) ); |
| 113 | + } |
| 114 | + |
| 115 | + if ( $contest === false ) { |
| 116 | + $this->getOutput()->redirect( SpecialPage::getTitleFor( 'Contests' )->getLocalURL() ); |
| 117 | + } |
| 118 | + else { |
| 119 | + if ( !$isNew ) { |
| 120 | + $this->getOutput()->addHTML( |
| 121 | + SpecialContestPage::getNavigation( $contest->getField( 'name' ), $this->getUser(), $this->getLang(), $this->getName() ) |
| 122 | + ); |
| 123 | + } |
| 124 | + |
| 125 | + $this->contest = $contest; |
| 126 | + $this->showForm(); |
| 127 | + $this->getOutput()->addModules( 'contest.special.editcontest' ); |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + /** |
| 132 | + * (non-PHPdoc) |
| 133 | + * @see FormSpecialPage::getForm() |
| 134 | + * @return HTMLForm|null |
| 135 | + */ |
| 136 | + protected function getForm() { |
| 137 | + $form = parent::getForm(); |
| 138 | + |
| 139 | + $form->addButton( |
| 140 | + 'cancelEdit', |
| 141 | + wfMsg( 'cancel' ), |
| 142 | + 'cancelEdit', |
| 143 | + array( |
| 144 | + 'target-url' => SpecialPage::getTitleFor( 'Contests' )->getFullURL() |
| 145 | + ) |
| 146 | + ); |
| 147 | + |
| 148 | +// $form->addButton( |
| 149 | +// 'deleteEdit', |
| 150 | +// wfMsg( 'delete' ), |
| 151 | +// 'deleteEdit' |
| 152 | +// ); |
| 153 | + |
| 154 | + return $form; |
| 155 | + } |
| 156 | + |
| 157 | + /** |
| 158 | + * (non-PHPdoc) |
| 159 | + * @see FormSpecialPage::getFormFields() |
| 160 | + * @return array |
| 161 | + */ |
| 162 | + protected function getFormFields() { |
| 163 | + $contest = $this->contest; |
| 164 | + |
| 165 | + $fields = array(); |
| 166 | + |
| 167 | + $fields['id'] = array ( 'type' => 'hidden' ); |
| 168 | + |
| 169 | + $fields['name'] = array ( |
| 170 | + 'type' => 'text', |
| 171 | + 'label-message' => 'contest-edit-name', |
| 172 | + 'id' => 'contest-name-field', |
| 173 | + ); |
| 174 | + |
| 175 | + $fields['status'] = array ( |
| 176 | + 'type' => 'radio', |
| 177 | + 'label-message' => 'contest-edit-status', |
| 178 | + 'options' => Contest::getStatusMessages( true ) |
| 179 | + ); |
| 180 | + |
| 181 | + $fields['intro'] = array ( |
| 182 | + 'type' => 'text', |
| 183 | + 'label-message' => 'contest-edit-intro', |
| 184 | + ); |
| 185 | + |
| 186 | + $fields['opportunities'] = array ( |
| 187 | + 'type' => 'text', |
| 188 | + 'label-message' => 'contest-edit-opportunities', |
| 189 | + ); |
| 190 | + |
| 191 | + $fields['rules_page'] = array ( |
| 192 | + 'type' => 'text', |
| 193 | + 'label-message' => 'contest-edit-rulespage', |
| 194 | + ); |
| 195 | + |
| 196 | + $fields['help'] = array ( |
| 197 | + 'type' => 'text', |
| 198 | + 'label-message' => 'contest-edit-help', |
| 199 | + ); |
| 200 | + |
| 201 | + $fields['signup_email'] = array ( |
| 202 | + 'type' => 'text', |
| 203 | + 'label-message' => 'contest-edit-signup', |
| 204 | + ); |
| 205 | + |
| 206 | + $fields['reminder_email'] = array ( |
| 207 | + 'type' => 'text', |
| 208 | + 'label-message' => 'contest-edit-reminder', |
| 209 | + ); |
| 210 | + |
| 211 | + $fields['end'] = array ( |
| 212 | + 'type' => 'text', |
| 213 | + 'label-message' => 'contest-edit-end', |
| 214 | + 'id' => 'contest-edit-end', |
| 215 | + 'size' => 15 |
| 216 | + ); |
| 217 | + |
| 218 | + if ( $contest !== false ) { |
| 219 | + foreach ( $fields as $name => $data ) { |
| 220 | + $default = $contest->getField( $name ); |
| 221 | + |
| 222 | + if ( $name == 'end' ) { |
| 223 | + $default = wfTimestamp( TS_DB, $default ); |
| 224 | + } |
| 225 | + |
| 226 | + $fields[$name]['default'] = $default; |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + $mappedFields = array(); |
| 231 | + |
| 232 | + foreach ( $fields as $name => $field ) { |
| 233 | + $mappedFields['contest-' . $name] = $field; |
| 234 | + } |
| 235 | + |
| 236 | + if ( $contest !== false ) { |
| 237 | + foreach ( $contest->getChallenges() as /* ContestChallenge */ $challenge ) { |
| 238 | + $mappedFields[] = array( |
| 239 | + 'class' => 'ContestChallengeField', |
| 240 | + 'options' => $challenge->toArray() |
| 241 | + ); |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + $mappedFields['delete-challenges'] = array ( 'type' => 'hidden', 'id' => 'delete-challenges' ); |
| 246 | + |
| 247 | + return $mappedFields; |
| 248 | + } |
| 249 | + |
| 250 | + /** |
| 251 | + * Process the form. At this point we know that the user passes all the criteria in |
| 252 | + * userCanExecute(), and if the data array contains 'Username', etc, then Username |
| 253 | + * resets are allowed. |
| 254 | + * |
| 255 | + * @param array $data |
| 256 | + * |
| 257 | + * @return Bool|Array |
| 258 | + */ |
| 259 | + public function onSubmit( array $data ) { |
| 260 | + $fields = array(); |
| 261 | + |
| 262 | + foreach ( $data as $name => $value ) { |
| 263 | + $matches = array(); |
| 264 | + |
| 265 | + if ( preg_match( '/contest-(.+)/', $name, $matches ) ) { |
| 266 | + if ( $matches[1] == 'end' ) { |
| 267 | + $value = wfTimestamp( TS_MW, strtotime( $value ) ); |
| 268 | + } |
| 269 | + |
| 270 | + $fields[$matches[1]] = $value; |
| 271 | + } |
| 272 | + } |
| 273 | + |
| 274 | + // If no ID is set, this means it's a new contest, so set the ID to null for an insert. |
| 275 | + // However, the user can have hot the back button after creation of a new contest, |
| 276 | + // re-submitting the form. In this case, get the ID of the already existing item for an update. |
| 277 | + if ( !array_key_exists( 'id', $fields ) || $fields['id'] === '' ) { |
| 278 | + $contest = Contest::s()->selectRow( 'id', array( 'name' => $fields['name'] ) ); |
| 279 | + $fields['id'] = $contest === false ? null : $contest->getField( 'id' ); |
| 280 | + } |
| 281 | + |
| 282 | + $contest = new Contest( $fields, true ); |
| 283 | + |
| 284 | + $contest->setChallenges( $this->getSubmittedChallenges() ); |
| 285 | + $success = $contest->writeAllToDB(); |
| 286 | + |
| 287 | + $success = $this->removeDeletedChallenges( $data['delete-challenges'] ) && $success; |
| 288 | + |
| 289 | + if ( $success ) { |
| 290 | + return true; |
| 291 | + } |
| 292 | + else { |
| 293 | + return array(); // TODO |
| 294 | + } |
| 295 | + } |
| 296 | + |
| 297 | + /** |
| 298 | + * The UI keeps track of 'removed' challenges by storing them into a |
| 299 | + * hidden HTML input, pipe-separated. On submission, this method |
| 300 | + * takes this string and actually deletes them. |
| 301 | + * |
| 302 | + * @since 0.1 |
| 303 | + * |
| 304 | + * @param string $idString |
| 305 | + * |
| 306 | + * @return boolean Success indicator |
| 307 | + */ |
| 308 | + protected function removeDeletedChallenges( $idString ) { |
| 309 | + if ( $idString == '' ) { |
| 310 | + return true; |
| 311 | + } |
| 312 | + |
| 313 | + return ContestChallenge::s()->delete( array( 'id' => explode( '|', $idString ) ) ); |
| 314 | + } |
| 315 | + |
| 316 | + /** |
| 317 | + * Finds the submitted challanges and returns them as a list of |
| 318 | + * ContestChallenge objects. |
| 319 | + * |
| 320 | + * @since 0.1 |
| 321 | + * |
| 322 | + * @return array of ContestChallenge |
| 323 | + */ |
| 324 | + protected function getSubmittedChallenges() { |
| 325 | + $challenges = array(); |
| 326 | + |
| 327 | + foreach ( $this->getrequest()->getValues() as $name => $value ) { |
| 328 | + $matches = array(); |
| 329 | + |
| 330 | + if ( preg_match( '/contest-challenge-(\d+)/', $name, $matches ) ) { |
| 331 | + $challenges[] = $this->getSubmittedChallenge( $matches[1] ); |
| 332 | + } elseif ( preg_match( '/contest-challenge-new-(\d+)/', $name, $matches ) ) { |
| 333 | + $challenges[] = $this->getSubmittedChallenge( $matches[1], true ); |
| 334 | + } |
| 335 | + } |
| 336 | + |
| 337 | + return $challenges; |
| 338 | + } |
| 339 | + |
| 340 | + /** |
| 341 | + * Create and return a contest challenge object from the submitted data. |
| 342 | + * |
| 343 | + * @since 0.1 |
| 344 | + * |
| 345 | + * @param integer|null $challengeId |
| 346 | + * |
| 347 | + * @return ContestChallenge |
| 348 | + */ |
| 349 | + protected function getSubmittedChallenge( $challengeId, $isNewChallenge = false ) { |
| 350 | + if ( $isNewChallenge ) { |
| 351 | + $challengeDbId = null; |
| 352 | + $challengeId = "new-$challengeId"; |
| 353 | + } else { |
| 354 | + $challengeDbId = $challengeId; |
| 355 | + } |
| 356 | + |
| 357 | + $request = $this->getRequest(); |
| 358 | + |
| 359 | + return new ContestChallenge( array( |
| 360 | + 'id' => $challengeDbId, |
| 361 | + 'text' => $request->getText( "challenge-text-$challengeId" ), |
| 362 | + 'title' => $request->getText( "contest-challenge-$challengeId" ), |
| 363 | + 'oneline' => $request->getText( "challenge-oneline-$challengeId" ), |
| 364 | + ) ); |
| 365 | + } |
| 366 | + |
| 367 | + public function onSuccess() { |
| 368 | + $this->getOutput()->redirect( SpecialPage::getTitleFor( 'Contests' )->getLocalURL() ); |
| 369 | + } |
| 370 | + |
| 371 | + /** |
| 372 | + * Show a message in a warning box. |
| 373 | + * |
| 374 | + * @since 0.1 |
| 375 | + * |
| 376 | + * @param string $message |
| 377 | + */ |
| 378 | + protected function showWarning( $message ) { |
| 379 | + $this->getOutput()->addHTML( |
| 380 | + '<p class="visualClear warningbox">' . wfMsgExt( $message, 'parseinline' ) . '</p>' |
| 381 | + ); |
| 382 | + } |
| 383 | + |
| 384 | +} |
| 385 | + |
| 386 | +class ContestChallengeField extends HTMLFormField { |
| 387 | + |
| 388 | + public function getInputHTML( $value ) { |
| 389 | + $attribs = array( |
| 390 | + 'class' => 'contest-challenge' |
| 391 | + ); |
| 392 | + |
| 393 | + foreach ( $this->mParams['options'] as $name => $value ) { |
| 394 | + $attribs['data-challenge-' . $name] = $value; |
| 395 | + } |
| 396 | + |
| 397 | + return Html::element( |
| 398 | + 'div', |
| 399 | + $attribs |
| 400 | + ); |
| 401 | + } |
| 402 | + |
| 403 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialEditContest.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 404 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialMyContests.php |
— | — | @@ -0,0 +1,547 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * List of contests for a user. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialMyContests.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialMyContests extends SpecialContestPage { |
| 16 | + |
| 17 | + protected $submissionState = null; |
| 18 | + |
| 19 | + /** |
| 20 | + * Constructor. |
| 21 | + * |
| 22 | + * @since 0.1 |
| 23 | + */ |
| 24 | + public function __construct() { |
| 25 | + parent::__construct( 'MyContests', 'contestparticipant' ); |
| 26 | + } |
| 27 | + |
| 28 | + /** |
| 29 | + * Main method. |
| 30 | + * |
| 31 | + * @since 0.1 |
| 32 | + * |
| 33 | + * @param string $arg |
| 34 | + */ |
| 35 | + public function execute( $subPage ) { |
| 36 | + $subPage = str_replace( '_', ' ', $subPage ); |
| 37 | + |
| 38 | + if ( !parent::execute( $subPage ) ) { |
| 39 | + return; |
| 40 | + } |
| 41 | + |
| 42 | + if ( $this->getRequest()->wasPosted() ) { |
| 43 | + $contestant = ContestContestant::s()->selectRow( null, array( 'id' => $this->getRequest()->getInt( 'wpcontestant-id' ) ) ); |
| 44 | + $this->showSubmissionPage( $contestant ); |
| 45 | + } |
| 46 | + else { |
| 47 | + if ( $subPage == '' ) { |
| 48 | + $this->displayContestsOverview(); |
| 49 | + } |
| 50 | + else { |
| 51 | + $this->handleSubmissionView( $subPage ); |
| 52 | + } |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + /** |
| 57 | + * On regular page view, ie no submission and no sub-page, |
| 58 | + * display a list of all contests the user is participating in, |
| 59 | + * or in case there is only one, redirect them to the submissiom |
| 60 | + * UI of it. |
| 61 | + * |
| 62 | + * @since 0.1 |
| 63 | + */ |
| 64 | + protected function displayContestsOverview() { |
| 65 | + $contestants = ContestContestant::s()->select( |
| 66 | + array( 'id', 'contest_id', 'challenge_id' ), |
| 67 | + array( 'user_id' => $this->getUser()->getId() ) |
| 68 | + ); |
| 69 | + |
| 70 | + $contestantCount = count( $contestants ); |
| 71 | + |
| 72 | + if ( $contestantCount == 0 ) { |
| 73 | + $this->getOutput()->addWikiMsg( 'contest-mycontests-no-contests' ); |
| 74 | + } |
| 75 | + else if ( $contestantCount == 1 ) { |
| 76 | + |
| 77 | + /** |
| 78 | + * @var $contest Contest |
| 79 | + */ |
| 80 | + $contest = $contestants[0]->getContest( array( 'status', 'name' ) ); |
| 81 | + |
| 82 | + if ( $contest->getField( 'status' ) == Contest::STATUS_ACTIVE ) { |
| 83 | + $this->getOutput()->redirect( $this->getTitle( $contest->getField( 'name' ) )->getLocalURL() ); |
| 84 | + } |
| 85 | + else { |
| 86 | + $this->displayContestsTable( $contestants ); |
| 87 | + } |
| 88 | + } |
| 89 | + else { |
| 90 | + $this->displayContestsTable( $contestants ); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + /** |
| 95 | + * Displays a list of contests the user participates or participated in, |
| 96 | + * together with their user specific choices such as the contest challenge. |
| 97 | + * |
| 98 | + * @since 0.1 |
| 99 | + * |
| 100 | + * @param array $contestants |
| 101 | + */ |
| 102 | + protected function displayContestsTable( array /* of ContestContestant */ $contestants ) { |
| 103 | + $running = array(); |
| 104 | + $passed = array(); |
| 105 | + $contests = array(); |
| 106 | + |
| 107 | + foreach ( $contestants as $contestant ) { |
| 108 | + /** |
| 109 | + * @var $contest Contest |
| 110 | + */ |
| 111 | + $contest = $contestant->getContest(); |
| 112 | + |
| 113 | + if ( $contest->getField( 'status' ) == Contest::STATUS_ACTIVE ) { |
| 114 | + $running[] = $contestant; |
| 115 | + } |
| 116 | + else if ( $contest->getField( 'status' ) == Contest::STATUS_FINISHED ) { |
| 117 | + $passed[] = $contestant; |
| 118 | + } |
| 119 | + |
| 120 | + $contests[$contest->getId()] = $contest; |
| 121 | + } |
| 122 | + |
| 123 | + if ( count( $running ) > 0 ) { |
| 124 | + $this->displayRunningContests( $running, $contests ); |
| 125 | + } |
| 126 | + |
| 127 | + if ( count( $passed ) > 0 ) { |
| 128 | + //$this->displayPassedContests( $passed, $contests ); |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Display a table with the running (active) contests for this user. |
| 134 | + * |
| 135 | + * @since 0.1 |
| 136 | + * |
| 137 | + * @param array $contestants |
| 138 | + * @param array $contests |
| 139 | + */ |
| 140 | + protected function displayRunningContests( array /* of ContestContestant */ $contestants, array /* Contest */ $contests ) { |
| 141 | + $out = $this->getOutput(); |
| 142 | + |
| 143 | + $out->addHTML( Html::element( 'h2', array(), wfMsg( 'contest-mycontests-active-header' ) ) ); |
| 144 | + $out->addHTML( Html::element( 'p', array(), wfMsg( 'contest-mycontests-active-text' ) ) ); |
| 145 | + |
| 146 | + $out->addHTML( Xml::openElement( |
| 147 | + 'table', |
| 148 | + array( 'class' => 'wikitable sortable' ) |
| 149 | + ) ); |
| 150 | + |
| 151 | + $headers = array( |
| 152 | + Html::element( 'th', array(), wfMsg( 'contest-mycontests-header-contest' ) ), |
| 153 | + Html::element( 'th', array(), wfMsg( 'contest-mycontests-header-challenge' ) ), |
| 154 | + ); |
| 155 | + |
| 156 | + $out->addHTML( '<thead><tr>' . implode( '', $headers ) . '</tr></thead>' ); |
| 157 | + |
| 158 | + $out->addHTML( '<tbody>' ); |
| 159 | + |
| 160 | + foreach ( $contestants as $contestant ) { |
| 161 | + |
| 162 | + /** |
| 163 | + * @var $contestant ContestContestant |
| 164 | + */ |
| 165 | + |
| 166 | + /** |
| 167 | + * @var $contest Contest |
| 168 | + */ |
| 169 | + $contest = $contests[$contestant->getField( 'contest_id' )]; |
| 170 | + |
| 171 | + $challengeTitle = ContestChallenge::s()->selectRow( |
| 172 | + 'title', |
| 173 | + array( 'id' => $contestant->getField( 'challenge_id' ) ) |
| 174 | + )->getField( 'title' ); |
| 175 | + |
| 176 | + $fields = array(); |
| 177 | + |
| 178 | + $fields[] = Html::rawElement( 'td', array( 'data-sort-value' => $contest->getField( 'name' ) ), Html::rawElement( |
| 179 | + 'a', |
| 180 | + array( |
| 181 | + 'href' => SpecialPage::getTitleFor( 'MyContests', $contest->getField( 'name' ) )->getLocalURL() |
| 182 | + ), |
| 183 | + htmlspecialchars( $contest->getField( 'name' ) ) |
| 184 | + ) ); |
| 185 | + |
| 186 | + $fields[] = Html::element( 'td', array(), $challengeTitle ); |
| 187 | + |
| 188 | + $out->addHTML( '<tr>' . implode( '', $fields ) . '</tr>' ); |
| 189 | + } |
| 190 | + |
| 191 | + $out->addHTML( '</tbody>' ); |
| 192 | + $out->addHTML( '</table>' ); |
| 193 | + } |
| 194 | + |
| 195 | + /** |
| 196 | + * Display a table with the passed (finished) contests for this user. |
| 197 | + * |
| 198 | + * @since 0.1 |
| 199 | + * |
| 200 | + * @param array $contestants |
| 201 | + * @param array $contests |
| 202 | + */ |
| 203 | + protected function displayPassedContests( array /* of ContestContestant */ $contestants, array /* Contest */ $contests ) { |
| 204 | + $out = $this->getOutput(); |
| 205 | + |
| 206 | + $out->addHTML( Html::element( 'h2', array(), wfMsg( 'contest-mycontests-finished-header' ) ) ); |
| 207 | + $out->addHTML( Html::element( 'h2', array(), wfMsg( 'contest-mycontests-finished-text' ) ) ); |
| 208 | + |
| 209 | + // TODO |
| 210 | + } |
| 211 | + |
| 212 | + /** |
| 213 | + * Handle view requests for the page. |
| 214 | + * |
| 215 | + * @since 0.1 |
| 216 | + * |
| 217 | + * @param string $contestName |
| 218 | + */ |
| 219 | + protected function handleSubmissionView( $contestName ) { |
| 220 | + $out = $this->getOutput(); |
| 221 | + |
| 222 | + $contest = Contest::s()->selectRow( null, array( 'name' => $contestName ) ); |
| 223 | + |
| 224 | + if ( $contest === false ) { |
| 225 | + $this->showError( 'contest-submission-unknown' ); |
| 226 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 227 | + $out->returnToMain(); |
| 228 | + } |
| 229 | + else { |
| 230 | + switch ( $contest->getStatus() ) { |
| 231 | + case Contest::STATUS_ACTIVE: |
| 232 | + $this->handleEnabledPage( $contest ); |
| 233 | + break; |
| 234 | + case Contest::STATUS_FINISHED: |
| 235 | + case Contest::STATUS_EXPIRED: |
| 236 | + $this->showWarning( 'contest-submission-finished' ); |
| 237 | + $out->addHTML( '<br /><br /><br /><br />' ); |
| 238 | + $out->returnToMain(); |
| 239 | + break; |
| 240 | + } |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + /** |
| 245 | + * Handle page request when the contest is enabled. |
| 246 | + * |
| 247 | + * @since 0.1 |
| 248 | + * |
| 249 | + * @param Contest $contest |
| 250 | + */ |
| 251 | + protected function handleEnabledPage( Contest $contest ) { |
| 252 | + // Check if the user is already a contestant in this contest. |
| 253 | + // If he is, redirect to submission page, else show signup form. |
| 254 | + $contestant = ContestContestant::s()->selectRow( |
| 255 | + null, |
| 256 | + array( |
| 257 | + 'contest_id' => $contest->getId(), |
| 258 | + 'user_id' => $this->getUser()->getId() |
| 259 | + ) |
| 260 | + ); |
| 261 | + |
| 262 | + if ( $contestant === false ) { |
| 263 | + $this->getOutput()->redirect( SpecialPage::getTitleFor( 'ContestSignup', $contest->getField( 'name' ) )->getLocalURL() ); |
| 264 | + } |
| 265 | + else { |
| 266 | + $contestant->setContest( $contest ); |
| 267 | + $this->showSubmissionPage( $contestant ); |
| 268 | + } |
| 269 | + } |
| 270 | + |
| 271 | + /** |
| 272 | + * Show the page content. |
| 273 | + * |
| 274 | + * @since 0.1 |
| 275 | + * |
| 276 | + * @param ContestContestant $contestant |
| 277 | + */ |
| 278 | + protected function showSubmissionPage( ContestContestant $contestant ) { |
| 279 | + $request = $this->getRequest(); |
| 280 | + $contest = $contestant->getContest(); |
| 281 | + if ( $request->getCheck( 'new' ) ) { |
| 282 | + $this->showSuccess( 'contest-mycontests-signup-success', $contest->getField( 'name' ) ); |
| 283 | + } |
| 284 | + else if ( $request->getCheck( 'added' ) ) { |
| 285 | + $this->showSuccess( 'contest-mycontests-addition-success' ); |
| 286 | + } |
| 287 | + else if ( $request->getCheck( 'updated' ) ) { |
| 288 | + $this->showSuccess( 'contest-mycontests-updated-success' ); |
| 289 | + } |
| 290 | + else if ( $request->wasPosted() |
| 291 | + && !$this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) { |
| 292 | + $this->showError( 'contest-mycontests-sessionfail' ); |
| 293 | + } |
| 294 | + |
| 295 | + $output = $this->getOutput(); |
| 296 | + $output->setPageTitle( $contest->getField( 'name' ) ); |
| 297 | + |
| 298 | + $output->addHTML('<div style="clear:both;"></div>'); |
| 299 | + $output->addWikiMsg( 'contest-submission-header', $contest->getField( 'name' ) ); |
| 300 | + |
| 301 | + $form = new HTMLForm( $this->getFormFields( $contestant ), $this->getContext() ); |
| 302 | + |
| 303 | + $form->setSubmitCallback( array( $this, 'handleSubmission' ) ); |
| 304 | + $form->setSubmitText( wfMsg( 'contest-submission-submit' ) ); |
| 305 | + |
| 306 | + if( $form->show() ) { |
| 307 | + $query = is_null( $this->submissionState ) ? '' : $this->submissionState; |
| 308 | + $output->redirect( $this->getTitle( $contest->getField( 'name' ) )->getLocalURL( $query ) ); |
| 309 | + } |
| 310 | + else { |
| 311 | + $output->addModules( 'contest.special.submission' ); |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + /** |
| 316 | + * Handle form submission. |
| 317 | + * |
| 318 | + * @since 0.1 |
| 319 | + * |
| 320 | + * @return true|array |
| 321 | + */ |
| 322 | + public function handleSubmission( array $data ) { |
| 323 | + $user = $this->getUser(); |
| 324 | + |
| 325 | + $user->setEmail( $data['contestant-email'] ); |
| 326 | + $user->setRealName( $data['contestant-realname'] ); |
| 327 | + $user->saveSettings(); |
| 328 | + |
| 329 | + $contestant = new ContestContestant( array( |
| 330 | + 'id' => $data['contestant-id'], |
| 331 | + |
| 332 | + 'full_name' => $data['contestant-realname'], |
| 333 | + 'email' => $data['contestant-email'], |
| 334 | + |
| 335 | + 'country' => $data['contestant-country'], |
| 336 | + 'volunteer' => $data['contestant-volunteer'], |
| 337 | + 'wmf' => $data['contestant-wmf'], |
| 338 | + 'cv' => $data['contestant-cv'], |
| 339 | + |
| 340 | + 'submission' => trim( $data['contestant-submission'] ), |
| 341 | + ) ); |
| 342 | + |
| 343 | + $success = $contestant->writeToDB(); |
| 344 | + |
| 345 | + if ( $success ) { |
| 346 | + if ( trim( $data['contestant-previous-submission'] ) === '' && trim( $data['contestant-submission'] ) !== '' ) { |
| 347 | + $this->submissionState = 'added'; |
| 348 | + } |
| 349 | + else { |
| 350 | + $this->submissionState = 'updated'; |
| 351 | + } |
| 352 | + } |
| 353 | + |
| 354 | + return $success; |
| 355 | + } |
| 356 | + |
| 357 | + /** |
| 358 | + * Gets the field definitions for the form. |
| 359 | + * |
| 360 | + * @since 0.1 |
| 361 | + * |
| 362 | + * @param ContestContestant $contest |
| 363 | + */ |
| 364 | + protected function getFormFields( ContestContestant $contestant ) { |
| 365 | + $fields = array(); |
| 366 | + |
| 367 | + $user = $this->getUser(); |
| 368 | + |
| 369 | + $fields['contestant-id'] = array( |
| 370 | + 'type' => 'hidden', |
| 371 | + 'default' => $contestant->getId(), |
| 372 | + 'id' => 'contest-id', |
| 373 | + ); |
| 374 | + |
| 375 | + $fields['contestant-previous-submission'] = array( |
| 376 | + 'type' => 'hidden', |
| 377 | + 'default' => $contestant->getField( 'submission' ), |
| 378 | + ); |
| 379 | + |
| 380 | + $fields['contestant-submission'] = array( |
| 381 | + 'class' => 'ContestSubmissionField', |
| 382 | + 'label-message' => 'contest-submission-submission', |
| 383 | + 'validation-callback' => array( __CLASS__, 'validateSubmissionField' ), |
| 384 | + 'options' => array( |
| 385 | + 'domains' => implode( '|', ContestSettings::get( 'submissionDomains' ) ), |
| 386 | + 'value' => $contestant->getField( 'submission' ) |
| 387 | + ) |
| 388 | + ); |
| 389 | + |
| 390 | + $fields['contestant-realname'] = array( |
| 391 | + 'type' => 'text', |
| 392 | + 'default' => $user->getRealName(), |
| 393 | + 'label-message' => 'contest-signup-realname', |
| 394 | + 'required' => true, |
| 395 | + 'validation-callback' => array( __CLASS__, 'validateNameField' ) |
| 396 | + ); |
| 397 | + |
| 398 | + $fields['contestant-email'] = array( |
| 399 | + 'type' => 'email', |
| 400 | + 'default' => $user->getEmail(), |
| 401 | + 'label-message' => 'contest-signup-email', |
| 402 | + 'required' => true, |
| 403 | + 'validation-callback' => array( __CLASS__, 'validateEmailField' ), |
| 404 | + ); |
| 405 | + |
| 406 | + $fields['contestant-country'] = array( |
| 407 | + 'type' => 'select', |
| 408 | + 'default' => $contestant->getField( 'country' ), |
| 409 | + 'label-message' => 'contest-signup-country', |
| 410 | + 'required' => true, |
| 411 | + 'options' => ContestContestant::getCountriesForInput() |
| 412 | + ); |
| 413 | + |
| 414 | + $fields['contestant-volunteer'] = array( |
| 415 | + 'type' => 'check', |
| 416 | + 'default' => $contestant->getField( 'volunteer' ), |
| 417 | + 'label-message' => 'contest-signup-volunteer', |
| 418 | + ); |
| 419 | + |
| 420 | + $fields['contestant-wmf'] = array( |
| 421 | + 'type' => 'check', |
| 422 | + 'default' => $contestant->getField( 'wmf' ), |
| 423 | + 'label-message' => 'contest-signup-wmf', |
| 424 | + ); |
| 425 | + |
| 426 | + $hasWMF = $contestant->hasField( 'wmf' ); |
| 427 | + |
| 428 | + $fields['contestant-cv'] = array( |
| 429 | + 'type' => $hasWMF && $contestant->getField( 'wmf' ) ? 'text' : 'hidden', |
| 430 | + 'default' => $hasWMF ? $contestant->getField( 'cv' ) : '', |
| 431 | + 'label-message' => 'contest-signup-cv', |
| 432 | + 'validation-callback' => array( __CLASS__, 'validateCVField' ), |
| 433 | + ); |
| 434 | + |
| 435 | + return $fields; |
| 436 | + } |
| 437 | + |
| 438 | + /** |
| 439 | + * HTMLForm field validation-callback for name field. |
| 440 | + * |
| 441 | + * @since 0.1 |
| 442 | + * |
| 443 | + * @param $value String |
| 444 | + * @param $alldata Array |
| 445 | + * |
| 446 | + * @return true|string |
| 447 | + */ |
| 448 | + public static function validateNameField( $value, $alldata = null ) { |
| 449 | + if ( strlen( $value ) < 2 ) { |
| 450 | + return wfMsg( 'contest-signup-invalid-name' ); |
| 451 | + } |
| 452 | + |
| 453 | + return true; |
| 454 | + } |
| 455 | + |
| 456 | + /** |
| 457 | + * HTMLForm field validation-callback for email field. |
| 458 | + * |
| 459 | + * @since 0.1 |
| 460 | + * |
| 461 | + * @param $value String |
| 462 | + * @param $alldata Array |
| 463 | + * |
| 464 | + * @return true|string |
| 465 | + */ |
| 466 | + public static function validateEmailField( $value, $alldata = null ) { |
| 467 | + if ( !Sanitizer::validateEmail( $value ) ) { |
| 468 | + return wfMsg( 'contest-signup-invalid-email' ); |
| 469 | + } |
| 470 | + |
| 471 | + return true; |
| 472 | + } |
| 473 | + |
| 474 | + /** |
| 475 | + * HTMLForm field validation-callback for cv field. |
| 476 | + * |
| 477 | + * @since 0.1 |
| 478 | + * |
| 479 | + * @param $value String |
| 480 | + * @param $alldata Array |
| 481 | + * |
| 482 | + * @return true|string |
| 483 | + */ |
| 484 | + public static function validateCVField( $value, $alldata = null ) { |
| 485 | + if ( trim( $value ) !== '' && filter_var( $value, FILTER_VALIDATE_URL ) === false ) { |
| 486 | + return wfMsg( 'contest-signup-invalid-cv' ); |
| 487 | + } |
| 488 | + |
| 489 | + return true; |
| 490 | + } |
| 491 | + |
| 492 | + /** |
| 493 | + * HTMLForm field validation-callback for the submissiom field. |
| 494 | + * Warning: regexes used! o_O |
| 495 | + * |
| 496 | + * @since 0.1 |
| 497 | + * |
| 498 | + * @param $value String |
| 499 | + * @param $alldata Array |
| 500 | + * |
| 501 | + * @return true|string |
| 502 | + */ |
| 503 | + public static function validateSubmissionField( $value, $alldata = null ) { |
| 504 | + $value = trim( $value ); |
| 505 | + |
| 506 | + if ( $value == '' ) { |
| 507 | + return true; |
| 508 | + } |
| 509 | + |
| 510 | + $allowedPatterns = array( |
| 511 | + // GitHub URLs such as https://github.com/JeroenDeDauw/smwcon/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53b |
| 512 | + // '@^https://github\.com/[a-zA-Z0-9-]+/[a-zA-Z0-9_-]+/tree/[a-zA-Z0-9]{40}$@i' |
| 513 | + ); |
| 514 | + |
| 515 | + foreach ( ContestSettings::get( 'submissionDomains' ) as $domain ) { |
| 516 | + $allowedPatterns[] = '@^https?://(([a-z0-9]+)\.)?' . str_replace( '.', '\.', $domain ) . '/.*$@i'; |
| 517 | + } |
| 518 | + |
| 519 | + foreach ( $allowedPatterns as $pattern ) { |
| 520 | + if ( preg_match( $pattern, $value ) ) { |
| 521 | + return true; |
| 522 | + } |
| 523 | + } |
| 524 | + |
| 525 | + return wfMsg( 'contest-submission-invalid-url' ); |
| 526 | + } |
| 527 | + |
| 528 | +} |
| 529 | + |
| 530 | +class ContestSubmissionField extends HTMLFormField { |
| 531 | + |
| 532 | + public function getInputHTML( $value ) { |
| 533 | + $attribs = array( |
| 534 | + 'class' => 'contest-submission', |
| 535 | + 'data-name' => $this->mName |
| 536 | + ); |
| 537 | + |
| 538 | + foreach ( $this->mParams['options'] as $name => $value ) { |
| 539 | + $attribs['data-' . $name] = $value; |
| 540 | + } |
| 541 | + |
| 542 | + return Html::element( |
| 543 | + 'div', |
| 544 | + $attribs |
| 545 | + ); |
| 546 | + } |
| 547 | + |
| 548 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/specials/SpecialMyContests.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 549 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/Contest.settings.php |
— | — | @@ -0,0 +1,80 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * File defining the settings for the Contest extension. |
| 6 | + * More info can be found at https://www.mediawiki.org/wiki/Extension:Contest#Settings |
| 7 | + * |
| 8 | + * NOTICE: |
| 9 | + * Changing one of these settings can be done by assigning to $egContestSettings, |
| 10 | + * AFTER the inclusion of the extension itself. |
| 11 | + * |
| 12 | + * @since 0.1 |
| 13 | + * |
| 14 | + * @file Contest.settings.php |
| 15 | + * @ingroup Contest |
| 16 | + * |
| 17 | + * @licence GNU GPL v3+ |
| 18 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 19 | + */ |
| 20 | +class ContestSettings { |
| 21 | + |
| 22 | + /** |
| 23 | + * Returns the default values for the settings. |
| 24 | + * setting name (string) => setting value (mixed) |
| 25 | + * |
| 26 | + * @since 0.1 |
| 27 | + * |
| 28 | + * @return array |
| 29 | + */ |
| 30 | + protected static function getDefaultSettings() { |
| 31 | + return array( |
| 32 | + 'voteValues' => range( 0, 5 ), |
| 33 | + 'enableTopLink' => true, |
| 34 | + 'submissionDomains' => array( 'github.com', 'gitorious.org', 'mediawiki.org' ), |
| 35 | + 'reminderJobSize' => 50 |
| 36 | + ); |
| 37 | + } |
| 38 | + |
| 39 | + /** |
| 40 | + * Retruns an array with all settings after making sure they are |
| 41 | + * initialized (ie set settings have been merged with the defaults). |
| 42 | + * setting name (string) => setting value (mixed) |
| 43 | + * |
| 44 | + * @since 0.1 |
| 45 | + * |
| 46 | + * @return array |
| 47 | + */ |
| 48 | + public static function getSettings() { |
| 49 | + static $settings = false; |
| 50 | + |
| 51 | + if ( $settings === false ) { |
| 52 | + $settings = array_merge( |
| 53 | + self::getDefaultSettings(), |
| 54 | + $GLOBALS['egContestSettings'] |
| 55 | + ); |
| 56 | + } |
| 57 | + |
| 58 | + return $settings; |
| 59 | + } |
| 60 | + |
| 61 | + /** |
| 62 | + * Gets the value of the specified setting. |
| 63 | + * |
| 64 | + * @since 0.1 |
| 65 | + * |
| 66 | + * @param string $settingName |
| 67 | + * |
| 68 | + * @throws MWException |
| 69 | + * @return mixed |
| 70 | + */ |
| 71 | + public static function get( $settingName ) { |
| 72 | + $settings = self::getSettings(); |
| 73 | + |
| 74 | + if ( !array_key_exists( $settingName, $settings ) ) { |
| 75 | + throw new MWException( 'Attempt to get non-existing setting "' . $settingName . '"' ); |
| 76 | + } |
| 77 | + |
| 78 | + return $settings[$settingName]; |
| 79 | + } |
| 80 | + |
| 81 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/Contest.settings.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 82 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/Contest.php |
— | — | @@ -0,0 +1,277 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Initialization file for the Contest extension. |
| 6 | + * |
| 7 | + * Documentation: https://www.mediawiki.org/wiki/Extension:Contest |
| 8 | + * Support https://www.mediawiki.org/wiki/Extension_talk:Contest |
| 9 | + * Source code: http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/Contest |
| 10 | + * |
| 11 | + * @file Contest.php |
| 12 | + * @ingroup Contest |
| 13 | + * |
| 14 | + * @licence GNU GPL v3+ |
| 15 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 16 | + */ |
| 17 | + |
| 18 | +/** |
| 19 | + * This documentation group collects source code files belonging to Contest. |
| 20 | + * |
| 21 | + * @defgroup Contest Contest |
| 22 | + */ |
| 23 | + |
| 24 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 25 | + die( 'Not an entry point.' ); |
| 26 | +} |
| 27 | + |
| 28 | +if ( version_compare( $wgVersion, '1.18c', '<' ) ) { // Needs to be 1.18c because version_compare() works in confusing ways |
| 29 | + die( '<b>Error:</b> Contest requires MediaWiki 1.18 or above.' ); |
| 30 | +} |
| 31 | + |
| 32 | +define( 'CONTEST_VERSION', '0.1alpha' ); |
| 33 | + |
| 34 | +$wgExtensionCredits['other'][] = array( |
| 35 | + 'path' => __FILE__, |
| 36 | + 'name' => 'Contest', |
| 37 | + 'version' => CONTEST_VERSION, |
| 38 | + 'author' => array( |
| 39 | + '[http://www.mediawiki.org/wiki/User:Jeroen_De_Dauw Jeroen De Dauw]', |
| 40 | + ), |
| 41 | + 'url' => 'http://www.mediawiki.org/wiki/Extension:Contest', |
| 42 | + 'descriptionmsg' => 'contest-desc' |
| 43 | +); |
| 44 | + |
| 45 | +// i18n |
| 46 | +$wgExtensionMessagesFiles['Contest'] = dirname( __FILE__ ) . '/Contest.i18n.php'; |
| 47 | +$wgExtensionMessagesFiles['ContestAlias'] = dirname( __FILE__ ) . '/Contest.alias.php'; |
| 48 | + |
| 49 | +// Autoloading |
| 50 | +$wgAutoloadClasses['ContestHooks'] = dirname( __FILE__ ) . '/Contest.hooks.php'; |
| 51 | +$wgAutoloadClasses['ContestSettings'] = dirname( __FILE__ ) . '/Contest.settings.php'; |
| 52 | + |
| 53 | +$wgAutoloadClasses['ApiContestQuery'] = dirname( __FILE__ ) . '/api/ApiContestQuery.php'; |
| 54 | +$wgAutoloadClasses['ApiDeleteContest'] = dirname( __FILE__ ) . '/api/ApiDeleteContest.php'; |
| 55 | +$wgAutoloadClasses['ApiMailContestants'] = dirname( __FILE__ ) . '/api/ApiMailContestants.php'; |
| 56 | +$wgAutoloadClasses['ApiQueryChallenges'] = dirname( __FILE__ ) . '/api/ApiQueryChallenges.php'; |
| 57 | +$wgAutoloadClasses['ApiQueryContestants'] = dirname( __FILE__ ) . '/api/ApiQueryContestants.php'; |
| 58 | +$wgAutoloadClasses['ApiQueryContestComments'] = dirname( __FILE__ ) . '/api/ApiQueryContestComments.php'; |
| 59 | +$wgAutoloadClasses['ApiQueryContests'] = dirname( __FILE__ ) . '/api/ApiQueryContests.php'; |
| 60 | + |
| 61 | +$wgAutoloadClasses['Contest'] = dirname( __FILE__ ) . '/includes/Contest.class.php'; |
| 62 | +$wgAutoloadClasses['ContestantPager'] = dirname( __FILE__ ) . '/includes/ContestantPager.php'; |
| 63 | +$wgAutoloadClasses['ContestChallenge'] = dirname( __FILE__ ) . '/includes/ContestChallenge.php'; |
| 64 | +$wgAutoloadClasses['ContestComment'] = dirname( __FILE__ ) . '/includes/ContestComment.php'; |
| 65 | +$wgAutoloadClasses['ContestContestant'] = dirname( __FILE__ ) . '/includes/ContestContestant.php'; |
| 66 | +$wgAutoloadClasses['ContestDBObject'] = dirname( __FILE__ ) . '/includes/ContestDBObject.php'; |
| 67 | +$wgAutoloadClasses['ContestReminderJob'] = dirname( __FILE__ ) . '/includes/ContestReminderJob.php'; |
| 68 | +$wgAutoloadClasses['ContestUtils'] = dirname( __FILE__ ) . '/includes/ContestUtils.php'; |
| 69 | +$wgAutoloadClasses['ContestVote'] = dirname( __FILE__ ) . '/includes/ContestVote.php'; |
| 70 | + |
| 71 | +$wgAutoloadClasses['SpecialContest'] = dirname( __FILE__ ) . '/specials/SpecialContest.php'; |
| 72 | +$wgAutoloadClasses['SpecialContestant'] = dirname( __FILE__ ) . '/specials/SpecialContestant.php'; |
| 73 | +$wgAutoloadClasses['SpecialContestPage'] = dirname( __FILE__ ) . '/specials/SpecialContestPage.php'; |
| 74 | +$wgAutoloadClasses['SpecialContests'] = dirname( __FILE__ ) . '/specials/SpecialContests.php'; |
| 75 | +$wgAutoloadClasses['SpecialContestSignup'] = dirname( __FILE__ ) . '/specials/SpecialContestSignup.php'; |
| 76 | +$wgAutoloadClasses['SpecialContestWelcome'] = dirname( __FILE__ ) . '/specials/SpecialContestWelcome.php'; |
| 77 | +$wgAutoloadClasses['SpecialEditContest'] = dirname( __FILE__ ) . '/specials/SpecialEditContest.php'; |
| 78 | +$wgAutoloadClasses['SpecialMyContests'] = dirname( __FILE__ ) . '/specials/SpecialMyContests.php'; |
| 79 | + |
| 80 | +// Special pages |
| 81 | +$wgSpecialPages['Contest'] = 'SpecialContest'; |
| 82 | +$wgSpecialPages['Contestant'] = 'SpecialContestant'; |
| 83 | +$wgSpecialPages['Contests'] = 'SpecialContests'; |
| 84 | +$wgSpecialPages['ContestSignup'] = 'SpecialContestSignup'; |
| 85 | +$wgSpecialPages['ContestWelcome'] = 'SpecialContestWelcome'; |
| 86 | +$wgSpecialPages['EditContest'] = 'SpecialEditContest'; |
| 87 | +$wgSpecialPages['MyContests'] = 'SpecialMyContests'; |
| 88 | + |
| 89 | +$wgSpecialPageGroups['Contest'] = 'contest'; |
| 90 | +$wgSpecialPageGroups['Contestant'] = 'contest'; |
| 91 | +$wgSpecialPageGroups['Contests'] = 'contest'; |
| 92 | +$wgSpecialPageGroups['ContestSignup'] = 'contest'; |
| 93 | +$wgSpecialPageGroups['ContestWelcome'] = 'contest'; |
| 94 | +$wgSpecialPageGroups['EditContest'] = 'contest'; |
| 95 | +$wgSpecialPageGroups['MyContests'] = 'contest'; |
| 96 | + |
| 97 | +// API |
| 98 | +$wgAPIModules['deletecontest'] = 'ApiDeleteContest'; |
| 99 | +$wgAPIModules['mailcontestants'] = 'ApiMailContestants'; |
| 100 | +$wgAPIListModules['challenges'] = 'ApiQueryChallenges'; |
| 101 | +$wgAPIListModules['contestants'] = 'ApiQueryContestants'; |
| 102 | +$wgAPIListModules['contestcomments'] = 'ApiQueryContestComments'; |
| 103 | +$wgAPIListModules['contests'] = 'ApiQueryContests'; |
| 104 | + |
| 105 | +// Hooks |
| 106 | +$wgHooks['LoadExtensionSchemaUpdates'][] = 'ContestHooks::onSchemaUpdate'; |
| 107 | +$wgHooks['UnitTestsList'][] = 'ContestHooks::registerUnitTests'; |
| 108 | +$wgHooks['UserSetEmail'][] = 'ContestHooks::onUserSetEmail'; |
| 109 | +$wgHooks['PersonalUrls'][] = 'ContestHooks::onPersonalUrls'; |
| 110 | +$wgHooks['GetPreferences'][] = 'ContestHooks::onGetPreferences'; |
| 111 | +$wgHooks['LinkEnd'][] = 'ContestHooks::onLinkEnd'; |
| 112 | + |
| 113 | +// Rights |
| 114 | + |
| 115 | +$wgAvailableRights[] = 'contestadmin'; |
| 116 | +$wgAvailableRights[] = 'contestparticipant'; |
| 117 | +$wgAvailableRights[] = 'contestjudge'; |
| 118 | + |
| 119 | +# Users that can manage the contests. |
| 120 | +$wgGroupPermissions['*' ]['contestadmin'] = false; |
| 121 | +//$wgGroupPermissions['user' ]['contestadmin'] = false; |
| 122 | +//$wgGroupPermissions['autoconfirmed']['contestadmin'] = false; |
| 123 | +//$wgGroupPermissions['bot' ]['contestadmin'] = false; |
| 124 | +$wgGroupPermissions['sysop' ]['contestadmin'] = true; |
| 125 | +$wgGroupPermissions['contestadmin' ]['contestadmin'] = true; |
| 126 | + |
| 127 | +# Users that can be contest participants. |
| 128 | +$wgGroupPermissions['*' ]['contestparticipant'] = false; |
| 129 | +$wgGroupPermissions['user' ]['contestparticipant'] = true; |
| 130 | +//$wgGroupPermissions['autoconfirmed']['contestparticipant'] = true; |
| 131 | +//$wgGroupPermissions['bot' ]['contestparticipant'] = false; |
| 132 | +$wgGroupPermissions['sysop' ]['contestparticipant'] = true; |
| 133 | +$wgGroupPermissions['contestparticipant']['contestparticipant'] = true; |
| 134 | + |
| 135 | +# Users that can vote and comment on submissions. |
| 136 | +$wgGroupPermissions['*' ]['contestjudge'] = false; |
| 137 | +//$wgGroupPermissions['user' ]['contestjudge'] = false; |
| 138 | +//$wgGroupPermissions['autoconfirmed']['contestjudge'] = false; |
| 139 | +//$wgGroupPermissions['bot' ]['contestjudge'] = false; |
| 140 | +$wgGroupPermissions['sysop' ]['contestjudge'] = true; |
| 141 | +$wgGroupPermissions['contestjudge' ]['contestjudge'] = true; |
| 142 | + |
| 143 | + |
| 144 | +// Resource loader modules |
| 145 | +$moduleTemplate = array( |
| 146 | + 'localBasePath' => dirname( __FILE__ ) . '/resources', |
| 147 | + 'remoteExtPath' => 'Contest/resources' |
| 148 | +); |
| 149 | + |
| 150 | +$wgResourceModules['contest.special.contests'] = $moduleTemplate + array( |
| 151 | + 'scripts' => array( |
| 152 | + 'contest.special.contests.js' |
| 153 | + ), |
| 154 | + 'messages' => array( |
| 155 | + 'contest-special-confirm-delete', |
| 156 | + 'contest-special-delete-failed', |
| 157 | + ) |
| 158 | +); |
| 159 | + |
| 160 | +$wgResourceModules['jquery.ui.timepicker'] = $moduleTemplate + array( |
| 161 | + 'scripts' => array( |
| 162 | + 'jquery.ui.timepicker.js', |
| 163 | + ), |
| 164 | + 'styles' => array( |
| 165 | + 'jquery.ui.timepicker.css', |
| 166 | + ), |
| 167 | + 'dependencies' => array( |
| 168 | + 'jquery.ui.slider', |
| 169 | + 'jquery.ui.datepicker' |
| 170 | + ) |
| 171 | +); |
| 172 | + |
| 173 | +$wgResourceModules['contest.special.editcontest'] = $moduleTemplate + array( |
| 174 | + 'scripts' => array( |
| 175 | + 'contest.special.editcontest.js', |
| 176 | + ), |
| 177 | + 'messages' => array( |
| 178 | + 'contest-edit-delete', |
| 179 | + 'contest-edit-add-first', |
| 180 | + 'contest-edit-add-another', |
| 181 | + 'contest-edit-confirm-delete', |
| 182 | + 'contest-edit-challenge-title', |
| 183 | + 'contest-edit-challenge-text', |
| 184 | + 'contest-edit-challenge-oneline', |
| 185 | + ), |
| 186 | + 'dependencies' => array( |
| 187 | + 'jquery.ui.button', |
| 188 | + 'jquery.ui.timepicker' |
| 189 | + ) |
| 190 | +); |
| 191 | + |
| 192 | +$wgResourceModules['jquery.contestChallenges'] = $moduleTemplate + array( |
| 193 | + 'scripts' => array( |
| 194 | + 'jquery.contestChallenges.js' |
| 195 | + ), |
| 196 | + 'messages' => array( |
| 197 | + 'contest-welcome-accept-challenge' |
| 198 | + ), |
| 199 | + 'dependencies' => array( |
| 200 | + 'jquery.ui.button' |
| 201 | + ) |
| 202 | +); |
| 203 | + |
| 204 | +$wgResourceModules['contest.special.welcome'] = $moduleTemplate + array( |
| 205 | + 'scripts' => array( |
| 206 | + 'contest.special.welcome.js' |
| 207 | + ), |
| 208 | + 'styles' => array( |
| 209 | + 'contest.special.welcome.css', |
| 210 | + ), |
| 211 | + 'dependencies' => array( |
| 212 | + 'jquery.contestChallenges', 'jquery.fancybox', |
| 213 | + ), |
| 214 | + 'messages' => array( |
| 215 | + 'contest-welcome-select-header', |
| 216 | + 'contest-welcome-rules', |
| 217 | + 'contest-welcome-rules-link', |
| 218 | + ) |
| 219 | +); |
| 220 | + |
| 221 | +$wgResourceModules['contest.special.signup'] = $moduleTemplate + array( |
| 222 | + 'scripts' => array( |
| 223 | + 'contest.special.signup.js', |
| 224 | + ), |
| 225 | + 'dependencies' => array( |
| 226 | + 'jquery.ui.button', 'jquery.fancybox', |
| 227 | + ) |
| 228 | +); |
| 229 | + |
| 230 | +$wgResourceModules['contest.special.submission'] = $moduleTemplate + array( |
| 231 | + 'scripts' => array( |
| 232 | + 'contest.special.submission.js', |
| 233 | + ), |
| 234 | + 'dependencies' => array( |
| 235 | + 'jquery.ui.button', 'jquery.contestSubmission', |
| 236 | + ), |
| 237 | +); |
| 238 | + |
| 239 | +$wgResourceModules['jquery.contestSubmission'] = $moduleTemplate + array( |
| 240 | + 'scripts' => array( |
| 241 | + 'jquery.contestSubmission.js', |
| 242 | + ), |
| 243 | + 'messages' => array( |
| 244 | + 'contest-submission-new-submission', |
| 245 | + 'contest-submission-current-submission', |
| 246 | + 'contest-submission-domains', |
| 247 | + ) |
| 248 | +); |
| 249 | + |
| 250 | +$wgResourceModules['contest.contestant.pager'] = $moduleTemplate + array( |
| 251 | + 'scripts' => array( |
| 252 | + 'contest.contestant.pager.js', |
| 253 | + ), |
| 254 | + 'styles' => array( |
| 255 | + 'contest.contestant.pager.css', |
| 256 | + ), |
| 257 | +); |
| 258 | + |
| 259 | +$wgResourceModules['contest.special.contestant'] = $moduleTemplate + array( |
| 260 | + 'styles' => array( |
| 261 | + 'contest.special.contestant.css', |
| 262 | + ), |
| 263 | +); |
| 264 | + |
| 265 | +$wgResourceModules['jquery.fancybox'] = $moduleTemplate + array( |
| 266 | + 'scripts' => array( |
| 267 | + 'fancybox/jquery.fancybox-1.3.4.js', |
| 268 | + ), |
| 269 | + 'styles' => array( |
| 270 | + 'fancybox/jquery.fancybox-1.3.4.css' |
| 271 | + ), |
| 272 | +); |
| 273 | + |
| 274 | +unset( $moduleTemplate ); |
| 275 | + |
| 276 | +$egContestSettings = array(); |
| 277 | + |
| 278 | +$wgContestEmailParse = false; |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/Contest.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 279 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/INSTALL |
— | — | @@ -0,0 +1,64 @@ |
| 2 | +These is the install file for the Contest extension. |
| 3 | + |
| 4 | +Extension page on mediawiki.org: https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | +Latest version of the install file: http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/Contest/INSTALL?view=co |
| 6 | + |
| 7 | + |
| 8 | +== Requirements == |
| 9 | + |
| 10 | +Contest requires: |
| 11 | + |
| 12 | +* MediaWiki 1.18 or above |
| 13 | +* PHP 5.2 or above |
| 14 | +* MySQL |
| 15 | + |
| 16 | +== Download == |
| 17 | + |
| 18 | +You can get the code directly from SVN. Tags can be obtained via |
| 19 | + |
| 20 | + svn checkout <nowiki>http://svn.wikimedia.org/svnroot/mediawiki/tags/extensions/Contest/REL_version</nowiki> |
| 21 | + |
| 22 | +Where 'version' is the version number of the tag, such as 0_1 (see the [http://svn.wikimedia.org/svnroot/mediawiki/tags/extensions/Contest/ available tags]). |
| 23 | +The latest code can be obtained from trunk: |
| 24 | + |
| 25 | + svn checkout <nowiki>http://svn.wikimedia.org/svnroot/mediawiki/trunk/extensions/Contest/</nowiki> |
| 26 | + |
| 27 | +== Installation == |
| 28 | + |
| 29 | +Once you have downloaded the code, place the ''Contest'' directory within your MediaWiki |
| 30 | +'extensions' directory. Then add the following code to your [[Manual:LocalSettings.php|LocalSettings.php]] file: |
| 31 | + |
| 32 | +# Contest |
| 33 | +require_once( "$IP/extensions/Contest/Contest.php" ); |
| 34 | + |
| 35 | +== Configuration == |
| 36 | + |
| 37 | +Configuration of Contest is done by assigning to $egContestSettings in your |
| 38 | +[[Manual:LocalSettings.php|LocalSettings.php]] file, AFTER the inclusion of the |
| 39 | +extension. The options are listed below and their default is set in the Contest settings file: |
| 40 | +http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/Contest/Contest.settings.php?view=markup |
| 41 | +You should NOT modify the settings file, but can have a look at it to get an idea of |
| 42 | +how to use the settings, in case the below descriptions do not suffice. |
| 43 | + |
| 44 | +{| class="wikitable sortable" |
| 45 | +! Name |
| 46 | +! Type |
| 47 | +! Default |
| 48 | +! Description |
| 49 | +|- |
| 50 | +| votevalues |
| 51 | +| array of integer |
| 52 | +| range( 0, 5 ) |
| 53 | +| Values that can be voted by judges on a participant |
| 54 | +|- |
| 55 | +| enableTopLink |
| 56 | +| boolean |
| 57 | +| True |
| 58 | +| Enable display of the top link to Special:MyContests |
| 59 | +|- |
| 60 | +| submissionDomains |
| 61 | +| array of string |
| 62 | +| array( 'github.com', 'gitorious.org' ) |
| 63 | +| Domains on which submissions can be placed |
| 64 | +|} |
| 65 | + |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/INSTALL |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 66 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/RELEASE-NOTES |
— | — | @@ -0,0 +1,17 @@ |
| 2 | +These are the release notes for the Contest extension. |
| 3 | + |
| 4 | +Extension page on mediawiki.org: https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | +Latest version of the release notes: http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/Contest/RELEASE-NOTES?view=co |
| 6 | + |
| 7 | + |
| 8 | +=== Version 0.1 === |
| 9 | +2011-10-xx |
| 10 | + |
| 11 | +Initial release with these features: |
| 12 | + |
| 13 | +* Admin interface for managing contests and their challenges. |
| 14 | +* Landing and signup pages for each contest. |
| 15 | +* Personal contest list and submission interface for each user. |
| 16 | +* Summary pages per contest listing contestants, which can be filtered and sorted. |
| 17 | +* Judging interface that allows for rating and commenting on each participant. |
| 18 | +* All contests, challenges, contestants, comments and votes can be queried and exported via the API. |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/RELEASE-NOTES |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 19 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestDBObject.php |
— | — | @@ -0,0 +1,805 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Abstract base class for representing objects that are stored in some DB table. |
| 6 | + * This is a modified copy of SurveyDBClass, backported to work with PHP 5.2, |
| 7 | + * and therefore missing all the awesome you get with late static binding. |
| 8 | + * |
| 9 | + * @since 0.1 |
| 10 | + * |
| 11 | + * @file ContestDBObject.php |
| 12 | + * @ingroup Contest |
| 13 | + * |
| 14 | + * @licence GNU GPL v3 or later |
| 15 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 16 | + */ |
| 17 | +abstract class ContestDBObject { |
| 18 | + |
| 19 | + /** |
| 20 | + * The fields of the object. |
| 21 | + * field name (w/o prefix) => value |
| 22 | + * |
| 23 | + * @since 0.1 |
| 24 | + * @var array |
| 25 | + */ |
| 26 | + protected $fields = array( 'id' => null ); |
| 27 | + |
| 28 | + /** |
| 29 | + * Constructor. |
| 30 | + * |
| 31 | + * @since 0.1 |
| 32 | + * |
| 33 | + * @param array|null $fields |
| 34 | + * @param boolean $loadDefaults |
| 35 | + */ |
| 36 | + public function __construct( $fields, $loadDefaults = false ) { |
| 37 | + if ( !is_array( $fields ) ) { |
| 38 | + $fields = array(); |
| 39 | + } |
| 40 | + |
| 41 | + if ( $loadDefaults ) { |
| 42 | + $fields = array_merge( $this->getDefaults(), $fields ); |
| 43 | + } |
| 44 | + |
| 45 | + $this->setFields( $fields ); |
| 46 | + } |
| 47 | + |
| 48 | + /** |
| 49 | + * Load the specified fields from the database. |
| 50 | + * |
| 51 | + * @since 0.1 |
| 52 | + * |
| 53 | + * @param array|null $fields |
| 54 | + * @param boolean $override |
| 55 | + * |
| 56 | + * @return Success indicator |
| 57 | + */ |
| 58 | + public function loadFields( $fields = null, $override = true ) { |
| 59 | + if ( is_null( $this->getId() ) ) { |
| 60 | + return false; |
| 61 | + } |
| 62 | + |
| 63 | + if ( is_null( $fields ) ) { |
| 64 | + $fields = array_keys( $this->getFieldTypes() ); |
| 65 | + } |
| 66 | + |
| 67 | + $results = $this->rawSelect( |
| 68 | + $this->getPrefixedFields( $fields ), |
| 69 | + array( $this->getPrefixedField( 'id' ) => $this->getId() ), |
| 70 | + array( 'LIMIT' => 1 ) |
| 71 | + ); |
| 72 | + |
| 73 | + foreach ( $results as $result ) { |
| 74 | + $this->setFields( $this->getFieldsFromDBResult( $result ), $override ); |
| 75 | + return true; |
| 76 | + } |
| 77 | + |
| 78 | + return false; |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * Returns the name of the database table objects of this type are stored in. |
| 83 | + * |
| 84 | + * @since 0.1 |
| 85 | + * |
| 86 | + * @return string |
| 87 | + */ |
| 88 | + public abstract function getDBTable(); |
| 89 | + |
| 90 | + /** |
| 91 | + * Gets the db field prefix. |
| 92 | + * |
| 93 | + * @since 0.1 |
| 94 | + * |
| 95 | + * @return string |
| 96 | + */ |
| 97 | + protected abstract function getFieldPrefix(); |
| 98 | + |
| 99 | + /** |
| 100 | + * Gets the value of a field. |
| 101 | + * |
| 102 | + * @since 0.1 |
| 103 | + * |
| 104 | + * @param string $name |
| 105 | + * |
| 106 | + * @throws MWException |
| 107 | + * @return mixed |
| 108 | + */ |
| 109 | + public function getField( $name ) { |
| 110 | + if ( $this->hasField( $name ) ) { |
| 111 | + return $this->fields[$name]; |
| 112 | + } else { |
| 113 | + throw new MWException( 'Attempted to get not-set field ' . $name ); |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Remove a field. |
| 119 | + * |
| 120 | + * @since 0.1 |
| 121 | + * |
| 122 | + * @param string $name |
| 123 | + */ |
| 124 | + public function removeField( $name ) { |
| 125 | + unset( $this->fields[$name] ); |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * Returns the objects database id. |
| 130 | + * |
| 131 | + * @since 0.1 |
| 132 | + * |
| 133 | + * @return integer|null |
| 134 | + */ |
| 135 | + public function getId() { |
| 136 | + return $this->getField( 'id' ); |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * Sets the objects database id. |
| 141 | + * |
| 142 | + * @since 0.1 |
| 143 | + * |
| 144 | + * @param integere|null $id |
| 145 | + */ |
| 146 | + public function setId( $id ) { |
| 147 | + return $this->setField( 'id', $id ); |
| 148 | + } |
| 149 | + |
| 150 | + /** |
| 151 | + * Gets if a certain field is set. |
| 152 | + * |
| 153 | + * @since 0.1 |
| 154 | + * |
| 155 | + * @param string $name |
| 156 | + * |
| 157 | + * @return boolean |
| 158 | + */ |
| 159 | + public function hasField( $name ) { |
| 160 | + return array_key_exists( $name, $this->fields ); |
| 161 | + } |
| 162 | + |
| 163 | + /** |
| 164 | + * Gets if the id field is set. |
| 165 | + * |
| 166 | + * @since 0.1 |
| 167 | + * |
| 168 | + * @return boolean |
| 169 | + */ |
| 170 | + public function hasIdField() { |
| 171 | + return $this->hasField( 'id' ) |
| 172 | + && !is_null( $this->getField( 'id' ) ); |
| 173 | + } |
| 174 | + |
| 175 | + /** |
| 176 | + * Sets multiple fields. |
| 177 | + * |
| 178 | + * @since 0.1 |
| 179 | + * |
| 180 | + * @param array $fields The fields to set |
| 181 | + * @param boolean $override Override already set fields with the provided values? |
| 182 | + */ |
| 183 | + public function setFields( array $fields, $override = true ) { |
| 184 | + foreach ( $fields as $name => $value ) { |
| 185 | + if ( $override || !$this->hasField( $name ) ) { |
| 186 | + $this->setField( $name, $value ); |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Gets the fields => values to write to the table. |
| 193 | + * |
| 194 | + * @since 0.1 |
| 195 | + * |
| 196 | + * @return array |
| 197 | + */ |
| 198 | + protected function getWriteValues() { |
| 199 | + $values = array(); |
| 200 | + |
| 201 | + foreach ( $this->getFieldTypes() as $name => $type ) { |
| 202 | + if ( array_key_exists( $name, $this->fields ) ) { |
| 203 | + $value = $this->fields[$name]; |
| 204 | + |
| 205 | + switch ( $type ) { |
| 206 | + case 'array': |
| 207 | + $value = serialize( (array)$value ); |
| 208 | + } |
| 209 | + |
| 210 | + $values[$this->getFieldPrefix() . $name] = $value; |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + return $values; |
| 215 | + } |
| 216 | + |
| 217 | + /** |
| 218 | + * Serializes the object to an associative array which |
| 219 | + * can then easily be converted into JSON or similar. |
| 220 | + * |
| 221 | + * @since 0.1 |
| 222 | + * |
| 223 | + * @param null|array $props |
| 224 | + * @param boolean $incNullId |
| 225 | + * |
| 226 | + * @return array |
| 227 | + */ |
| 228 | + public function toArray( $fields = null, $incNullId = false ) { |
| 229 | + $data = array(); |
| 230 | + $setFields = array(); |
| 231 | + |
| 232 | + if ( !is_array( $fields ) ) { |
| 233 | + $setFields = $this->getSetFieldNames(); |
| 234 | + } else { |
| 235 | + foreach ( $fields as $field ) { |
| 236 | + if ( $this->hasField( $field ) ) { |
| 237 | + $setFields[] = $field; |
| 238 | + } |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | + foreach ( $setFields as $field ) { |
| 243 | + if ( $incNullId || $field != 'id' || $this->hasIdField() ) { |
| 244 | + $data[$field] = $this->getField( $field ); |
| 245 | + } |
| 246 | + } |
| 247 | + |
| 248 | + return $data; |
| 249 | + } |
| 250 | + |
| 251 | + /** |
| 252 | + * Load the default values, via getDefaults. |
| 253 | + * |
| 254 | + * @since 0.1 |
| 255 | + * |
| 256 | + * @param boolean $override |
| 257 | + */ |
| 258 | + public function loadDefaults( $override = true ) { |
| 259 | + $this->setFields( $this->getDefaults(), $override ); |
| 260 | + } |
| 261 | + |
| 262 | + /** |
| 263 | + * Writes the answer to the database, either updating it |
| 264 | + * when it already exists, or inserting it when it doesn't. |
| 265 | + * |
| 266 | + * @since 0.1 |
| 267 | + * |
| 268 | + * @return boolean Success indicator |
| 269 | + */ |
| 270 | + public function writeToDB() { |
| 271 | + if ( $this->hasIdField() ) { |
| 272 | + return $this->updateInDB(); |
| 273 | + } else { |
| 274 | + return $this->insertIntoDB(); |
| 275 | + } |
| 276 | + } |
| 277 | + |
| 278 | + /** |
| 279 | + * Updates the object in the database. |
| 280 | + * |
| 281 | + * @since 0.1 |
| 282 | + * |
| 283 | + * @return boolean Success indicator |
| 284 | + */ |
| 285 | + protected function updateInDB() { |
| 286 | + $dbw = wfGetDB( DB_MASTER ); |
| 287 | + |
| 288 | + return $dbw->update( |
| 289 | + $this->getDBTable(), |
| 290 | + $this->getWriteValues(), |
| 291 | + array( $this->getFieldPrefix() . 'id' => $this->getId() ), |
| 292 | + __METHOD__ |
| 293 | + ); |
| 294 | + } |
| 295 | + |
| 296 | + /** |
| 297 | + * Inserts the object into the database. |
| 298 | + * |
| 299 | + * @since 0.1 |
| 300 | + * |
| 301 | + * @return boolean Success indicator |
| 302 | + */ |
| 303 | + protected function insertIntoDB() { |
| 304 | + $dbw = wfGetDB( DB_MASTER ); |
| 305 | + |
| 306 | + $result = $dbw->insert( |
| 307 | + $this->getDBTable(), |
| 308 | + $this->getWriteValues(), |
| 309 | + __METHOD__, |
| 310 | + array( 'IGNORE' ) |
| 311 | + ); |
| 312 | + |
| 313 | + $this->setField( 'id', $dbw->insertId() ); |
| 314 | + |
| 315 | + return $result; |
| 316 | + } |
| 317 | + |
| 318 | + /** |
| 319 | + * Removes the object from the database. |
| 320 | + * |
| 321 | + * @since 0.1 |
| 322 | + * |
| 323 | + * @return boolean Success indicator |
| 324 | + */ |
| 325 | + public function removeFromDB() { |
| 326 | + $sucecss = $this->delete( array( 'id' => $this->getId() ) ); |
| 327 | + |
| 328 | + if ( $sucecss ) { |
| 329 | + $this->setField( 'id', null ); |
| 330 | + } |
| 331 | + |
| 332 | + return $sucecss; |
| 333 | + } |
| 334 | + |
| 335 | + /** |
| 336 | + * Return the names of the fields. |
| 337 | + * |
| 338 | + * @since 0.1 |
| 339 | + * |
| 340 | + * @return array |
| 341 | + */ |
| 342 | + public function getFields() { |
| 343 | + return $this->fields; |
| 344 | + } |
| 345 | + |
| 346 | + /** |
| 347 | + * Return the names of the fields. |
| 348 | + * |
| 349 | + * @since 0.1 |
| 350 | + * |
| 351 | + * @return array |
| 352 | + */ |
| 353 | + public function getSetFieldNames() { |
| 354 | + return array_keys( $this->fields ); |
| 355 | + } |
| 356 | + |
| 357 | + /** |
| 358 | + * Sets the value of a field. |
| 359 | + * Strings can be provided for other types, |
| 360 | + * so this method can be called from unserialization handlers. |
| 361 | + * |
| 362 | + * @since 0.1 |
| 363 | + * |
| 364 | + * @param string $name |
| 365 | + * @param mixed $value |
| 366 | + * |
| 367 | + * @throws MWException |
| 368 | + */ |
| 369 | + public function setField( $name, $value ) { |
| 370 | + $fields = $this->getFieldTypes(); |
| 371 | + |
| 372 | + if ( array_key_exists( $name, $fields ) ) { |
| 373 | + switch ( $fields[$name] ) { |
| 374 | + case 'int': |
| 375 | + $value = (int)$value; |
| 376 | + break; |
| 377 | + case 'float': |
| 378 | + $value = (float)$value; |
| 379 | + break; |
| 380 | + case 'bool': |
| 381 | + if ( is_string( $value ) ) { |
| 382 | + $value = $value !== '0'; |
| 383 | + } elseif ( is_int( $value ) ) { |
| 384 | + $value = $value !== 0; |
| 385 | + } |
| 386 | + break; |
| 387 | + case 'array': |
| 388 | + if ( is_string( $value ) ) { |
| 389 | + $value = unserialize( $value ); |
| 390 | + } |
| 391 | + break; |
| 392 | + case 'id': |
| 393 | + if ( is_string( $value ) ) { |
| 394 | + $value = (int)$value; |
| 395 | + } |
| 396 | + break; |
| 397 | + } |
| 398 | + |
| 399 | + $this->fields[$name] = $value; |
| 400 | + } else { |
| 401 | + throw new MWException( 'Attempted to set unknown field ' . $name ); |
| 402 | + } |
| 403 | + } |
| 404 | + |
| 405 | + /** |
| 406 | + * Returns an array with the fields and their types this object contains. |
| 407 | + * This corresponds directly to the fields in the database, without prefix. |
| 408 | + * |
| 409 | + * field name => type |
| 410 | + * |
| 411 | + * Allowed types: |
| 412 | + * * id |
| 413 | + * * str |
| 414 | + * * int |
| 415 | + * * float |
| 416 | + * * bool |
| 417 | + * * array |
| 418 | + * |
| 419 | + * @since 0.1 |
| 420 | + * |
| 421 | + * @return array |
| 422 | + */ |
| 423 | + protected abstract function getFieldTypes(); |
| 424 | + |
| 425 | + /** |
| 426 | + * Returns a list of default field values. |
| 427 | + * field name => field value |
| 428 | + * |
| 429 | + * @since 0.1 |
| 430 | + * |
| 431 | + * @return array |
| 432 | + */ |
| 433 | + public abstract function getDefaults(); |
| 434 | + |
| 435 | + // |
| 436 | + // |
| 437 | + // All below methods ought to be static, but can't be since this would require LSB introduced in PHP 5.3. |
| 438 | + // |
| 439 | + // |
| 440 | + |
| 441 | + /** |
| 442 | + * Gets if the object can take a certain field. |
| 443 | + * |
| 444 | + * @since 0.1 |
| 445 | + * |
| 446 | + * @param string $name |
| 447 | + * |
| 448 | + * @return boolean |
| 449 | + */ |
| 450 | + public function canHasField( $name ) { |
| 451 | + return array_key_exists( $name, $this->getFieldTypes() ); |
| 452 | + } |
| 453 | + |
| 454 | + /** |
| 455 | + * Takes in a field or array of fields and returns an |
| 456 | + * array with their prefixed versions, ready for db usage. |
| 457 | + * |
| 458 | + * @since 0.1 |
| 459 | + * |
| 460 | + * @param array|string $fields |
| 461 | + * |
| 462 | + * @return array |
| 463 | + */ |
| 464 | + public function getPrefixedFields( $fields ) { |
| 465 | + $fields = (array)$fields; |
| 466 | + |
| 467 | + foreach ( $fields as &$field ) { |
| 468 | + $field = $this->getFieldPrefix() . $field; |
| 469 | + } |
| 470 | + |
| 471 | + return $fields; |
| 472 | + } |
| 473 | + |
| 474 | + /** |
| 475 | + * Takes in a field and returns an it's prefixed version, ready for db usage. |
| 476 | + * |
| 477 | + * @since 0.1 |
| 478 | + * |
| 479 | + * @param string $field |
| 480 | + * |
| 481 | + * @return string |
| 482 | + */ |
| 483 | + public function getPrefixedField( $field ) { |
| 484 | + return $this->getFieldPrefix() . $field; |
| 485 | + } |
| 486 | + |
| 487 | + /** |
| 488 | + * Takes in an associative array with field names as keys and |
| 489 | + * their values as value. The field names are prefixed with the |
| 490 | + * db field prefix. |
| 491 | + * |
| 492 | + * @since 0.1 |
| 493 | + * |
| 494 | + * @param array $values |
| 495 | + * |
| 496 | + * @return array |
| 497 | + */ |
| 498 | + public function getPrefixedValues( array $values ) { |
| 499 | + $prefixedValues = array(); |
| 500 | + |
| 501 | + foreach ( $values as $field => $value ) { |
| 502 | + $prefixedValues[$this->getFieldPrefix() . $field] = $value; |
| 503 | + } |
| 504 | + |
| 505 | + return $prefixedValues; |
| 506 | + } |
| 507 | + |
| 508 | + /** |
| 509 | + * Get an array with fields from a database result, |
| 510 | + * that can be fed directly to the constructor or |
| 511 | + * to setFields. |
| 512 | + * |
| 513 | + * @since 0.1 |
| 514 | + * |
| 515 | + * @param object $result |
| 516 | + * |
| 517 | + * @return array |
| 518 | + */ |
| 519 | + protected function getFieldsFromDBResult( $result ) { |
| 520 | + $result = (array)$result; |
| 521 | + $data = array(); |
| 522 | + $idFieldLength = strlen( $this->getFieldPrefix() ); |
| 523 | + |
| 524 | + foreach ( $result as $name => $value ) { |
| 525 | + $data[substr( $name, $idFieldLength )] = $value; |
| 526 | + } |
| 527 | + |
| 528 | + return $data; |
| 529 | + } |
| 530 | + |
| 531 | + /** |
| 532 | + * Get a new instance of the class from a database result. |
| 533 | + * |
| 534 | + * @since 0.1 |
| 535 | + * |
| 536 | + * @param object $result |
| 537 | + * |
| 538 | + * @return ContestDBObject |
| 539 | + */ |
| 540 | + public function newFromDBResult( $result ) { |
| 541 | + return $this->newFromArray( $this->getFieldsFromDBResult( $result ) ); |
| 542 | + } |
| 543 | + |
| 544 | + /** |
| 545 | + * Removes the object from the database. |
| 546 | + * |
| 547 | + * @since 0.1 |
| 548 | + * |
| 549 | + * @param array $conditions |
| 550 | + * |
| 551 | + * @return boolean Success indicator |
| 552 | + */ |
| 553 | + public function delete( array $conditions ) { |
| 554 | + return wfGetDB( DB_MASTER )->delete( |
| 555 | + $this->getDBTable(), |
| 556 | + $this->getPrefixedValues( $conditions ) |
| 557 | + ); |
| 558 | + } |
| 559 | + |
| 560 | + /** |
| 561 | + * Add an amount (can be negative) to the specified field (needs to be numeric). |
| 562 | + * |
| 563 | + * @since 0.1 |
| 564 | + * |
| 565 | + * @param string $field |
| 566 | + * @param integer $amount |
| 567 | + * |
| 568 | + * @return boolean Success indicator |
| 569 | + */ |
| 570 | + public function addToField( $field, $amount ) { |
| 571 | + if ( $amount == 0 ) { |
| 572 | + return true; |
| 573 | + } |
| 574 | + |
| 575 | + if ( !$this->hasIdField() ) { |
| 576 | + return false; |
| 577 | + } |
| 578 | + |
| 579 | + $absoluteAmount = abs( $amount ); |
| 580 | + $isNegative = $amount < 0; |
| 581 | + |
| 582 | + $dbw = wfGetDB( DB_MASTER ); |
| 583 | + |
| 584 | + $fullField = $this->getPrefixedField( $field ); |
| 585 | + |
| 586 | + $success = $dbw->update( |
| 587 | + $this->getDBTable(), |
| 588 | + array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ), |
| 589 | + array( $this->getPrefixedField( 'id' ) => $this->getId() ), |
| 590 | + __METHOD__ |
| 591 | + ); |
| 592 | + |
| 593 | + if ( $success && $this->hasField( $field ) ) { |
| 594 | + $this->setField( $field, $this->getField( $field ) + $amount ); |
| 595 | + } |
| 596 | + |
| 597 | + return $success; |
| 598 | + } |
| 599 | + |
| 600 | + /** |
| 601 | + * Selects the the specified fields of the records matching the provided |
| 602 | + * conditions. Field names get prefixed. |
| 603 | + * |
| 604 | + * @since 0.1 |
| 605 | + * |
| 606 | + * @param array|string|null $fields |
| 607 | + * @param array $conditions |
| 608 | + * @param array $options |
| 609 | + * |
| 610 | + * @return array of self |
| 611 | + */ |
| 612 | + public function select( $fields = null, array $conditions = array(), array $options = array() ) { |
| 613 | + if ( is_null( $fields ) ) { |
| 614 | + $fields = array_keys( $this->getFieldTypes() ); |
| 615 | + } |
| 616 | + |
| 617 | + $result = $this->rawSelect( |
| 618 | + $this->getPrefixedFields( $fields ), |
| 619 | + $this->getPrefixedValues( $conditions ), |
| 620 | + $options |
| 621 | + ); |
| 622 | + |
| 623 | + $objects = array(); |
| 624 | + |
| 625 | + foreach ( $result as $record ) { |
| 626 | + $objects[] = $this->newFromDBResult( $record ); |
| 627 | + } |
| 628 | + |
| 629 | + return $objects; |
| 630 | + } |
| 631 | + |
| 632 | + /** |
| 633 | + * Selects the the specified fields of the first matching record. |
| 634 | + * Field names get prefixed. |
| 635 | + * |
| 636 | + * @since 0.1 |
| 637 | + * |
| 638 | + * @param array|string|null $fields |
| 639 | + * @param array $conditions |
| 640 | + * @param array $options |
| 641 | + * |
| 642 | + * @return self|false |
| 643 | + */ |
| 644 | + public function selectRow( $fields = null, array $conditions = array(), array $options = array() ) { |
| 645 | + $options['LIMIT'] = 1; |
| 646 | + |
| 647 | + $objects = $this->select( $fields, $conditions, $options ); |
| 648 | + |
| 649 | + return count( $objects ) > 0 ? $objects[0] : false; |
| 650 | + } |
| 651 | + |
| 652 | + /** |
| 653 | + * Returns if there is at least one record matching the provided conditions. |
| 654 | + * Condition field names get prefixed. |
| 655 | + * |
| 656 | + * @since 0.1 |
| 657 | + * |
| 658 | + * @param array $conditions |
| 659 | + * |
| 660 | + * @return boolean |
| 661 | + */ |
| 662 | + public function has( array $conditions = array() ) { |
| 663 | + return $this->selectRow( array( 'id' ), $conditions ) !== false; |
| 664 | + } |
| 665 | + |
| 666 | + /** |
| 667 | + * Returns the amount of matching records. |
| 668 | + * Condition field names get prefixed. |
| 669 | + * |
| 670 | + * @since 0.1 |
| 671 | + * |
| 672 | + * @param array $conditions |
| 673 | + * @param array $options |
| 674 | + * |
| 675 | + * @return integer |
| 676 | + */ |
| 677 | + public function count( array $conditions = array(), array $options = array() ) { |
| 678 | + $res = $this->rawSelect( |
| 679 | + array( 'COUNT(*) AS rowcount' ), |
| 680 | + $this->getPrefixedValues( $conditions ), |
| 681 | + $options |
| 682 | + )->fetchObject(); |
| 683 | + |
| 684 | + return $res->rowcount; |
| 685 | + } |
| 686 | + |
| 687 | + /** |
| 688 | + * Selects the the specified fields of the records matching the provided |
| 689 | + * conditions. Field names do NOT get prefixed. |
| 690 | + * |
| 691 | + * @since 0.1 |
| 692 | + * |
| 693 | + * @param array|null $fields |
| 694 | + * @param array $conditions |
| 695 | + * @param array $options |
| 696 | + * |
| 697 | + * @return ResultWrapper |
| 698 | + */ |
| 699 | + public function rawSelect( $fields = null, array $conditions = array(), array $options = array() ) { |
| 700 | + $dbr = wfGetDB( DB_SLAVE ); |
| 701 | + |
| 702 | + return $dbr->select( |
| 703 | + $this->getDBTable(), |
| 704 | + $fields, |
| 705 | + count( $conditions ) == 0 ? '' : $conditions, |
| 706 | + __METHOD__, |
| 707 | + $options |
| 708 | + ); |
| 709 | + } |
| 710 | + |
| 711 | + /** |
| 712 | + * Update the records matching the provided conditions by |
| 713 | + * setting the fields that are keys in the $values patam to |
| 714 | + * their corresponding values. |
| 715 | + * |
| 716 | + * @since 0.1 |
| 717 | + * |
| 718 | + * @param array $values |
| 719 | + * @param array $conditions |
| 720 | + * |
| 721 | + * @return boolean Success indicator |
| 722 | + */ |
| 723 | + public function update( array $values, array $conditions = array() ) { |
| 724 | + $dbw = wfGetDB( DB_MASTER ); |
| 725 | + |
| 726 | + return $dbw->update( |
| 727 | + $this->getDBTable(), |
| 728 | + $this->getPrefixedValues( $values ), |
| 729 | + $this->getPrefixedValues( $conditions ), |
| 730 | + __METHOD__ |
| 731 | + ); |
| 732 | + } |
| 733 | + |
| 734 | + /** |
| 735 | + * Return the names of the fields. |
| 736 | + * |
| 737 | + * @since 0.1 |
| 738 | + * |
| 739 | + * @return array |
| 740 | + */ |
| 741 | + public function getFieldNames() { |
| 742 | + return array_keys( $this->getFieldTypes() ); |
| 743 | + } |
| 744 | + |
| 745 | + /** |
| 746 | + * Returns an array with the fields and their descriptions. |
| 747 | + * |
| 748 | + * field name => field description |
| 749 | + * |
| 750 | + * @since 0.1 |
| 751 | + * |
| 752 | + * @return array |
| 753 | + */ |
| 754 | + public function getFieldDescriptions() { |
| 755 | + return array(); |
| 756 | + } |
| 757 | + |
| 758 | + /** |
| 759 | + * Get API parameters for the fields supported by this object. |
| 760 | + * |
| 761 | + * @since 0.1 |
| 762 | + * |
| 763 | + * @param boolean $requireParams |
| 764 | + * @param boolean $setDefaults |
| 765 | + * |
| 766 | + * @return array |
| 767 | + */ |
| 768 | + public function getAPIParams( $requireParams = false, $setDefaults = false ) { |
| 769 | + $typeMap = array( |
| 770 | + 'id' => 'integer', |
| 771 | + 'int' => 'integer', |
| 772 | + 'float' => 'NULL', |
| 773 | + 'str' => 'string', |
| 774 | + 'bool' => 'integer', |
| 775 | + 'array' => 'string' |
| 776 | + ); |
| 777 | + |
| 778 | + $params = array(); |
| 779 | + $defaults = $this->getDefaults(); |
| 780 | + |
| 781 | + foreach ( $this->getFieldTypes() as $field => $type ) { |
| 782 | + if ( $field == 'id' ) { |
| 783 | + continue; |
| 784 | + } |
| 785 | + |
| 786 | + $hasDefault = array_key_exists( $field, $defaults ); |
| 787 | + |
| 788 | + $params[$field] = array( |
| 789 | + ApiBase::PARAM_TYPE => $typeMap[$type], |
| 790 | + ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault |
| 791 | + ); |
| 792 | + |
| 793 | + if ( $type == 'array' ) { |
| 794 | + $params[$field][ApiBase::PARAM_ISMULTI] = true; |
| 795 | + } |
| 796 | + |
| 797 | + if ( $setDefaults && $hasDefault ) { |
| 798 | + $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field]; |
| 799 | + $params[$field][ApiBase::PARAM_DFLT] = $default; |
| 800 | + } |
| 801 | + } |
| 802 | + |
| 803 | + return $params; |
| 804 | + } |
| 805 | + |
| 806 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestDBObject.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 807 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestVote.php |
— | — | @@ -0,0 +1,121 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class representing a single contest vote. |
| 6 | + * Votes can be made by judges on (submissions of) contestants. |
| 7 | + * |
| 8 | + * @since 0.1 |
| 9 | + * |
| 10 | + * @file ContestComment.php |
| 11 | + * @ingroup Contest |
| 12 | + * |
| 13 | + * @licence GNU GPL v3 or later |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ContestVote extends ContestDBObject { |
| 17 | + |
| 18 | + /** |
| 19 | + * Method to get an instance so methods that ought to be static, |
| 20 | + * but can't be due to PHP 5.2 not having LSB, can be called on |
| 21 | + * it. This also allows easy identifying of code that needs to |
| 22 | + * be changed once PHP 5.3 becomes an acceptable requirement. |
| 23 | + * |
| 24 | + * @since 0.1 |
| 25 | + * |
| 26 | + * @return ContestDBObject |
| 27 | + */ |
| 28 | + public static function s() { |
| 29 | + static $instance = false; |
| 30 | + |
| 31 | + if ( $instance === false ) { |
| 32 | + $instance = new self( array() ); |
| 33 | + } |
| 34 | + |
| 35 | + return $instance; |
| 36 | + } |
| 37 | + |
| 38 | + /** |
| 39 | + * Get a new instance of the class from an array. |
| 40 | + * This method ought to be in the basic class and |
| 41 | + * return a new static(), but this requires LSB/PHP>=5.3. |
| 42 | + * |
| 43 | + * @since 0.1 |
| 44 | + * |
| 45 | + * @param array $data |
| 46 | + * @param boolean $loadDefaults |
| 47 | + * |
| 48 | + * @return ContestDBObject |
| 49 | + */ |
| 50 | + public function newFromArray( array $data, $loadDefaults = false ) { |
| 51 | + return new self( $data, $loadDefaults ); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * @see parent::getFieldTypes |
| 56 | + * |
| 57 | + * @since 0.1 |
| 58 | + * |
| 59 | + * @return string |
| 60 | + */ |
| 61 | + public function getDBTable() { |
| 62 | + return 'contest_votes'; |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * @see parent::getFieldTypes |
| 67 | + * |
| 68 | + * @since 0.1 |
| 69 | + * |
| 70 | + * @return string |
| 71 | + */ |
| 72 | + protected function getFieldPrefix() { |
| 73 | + return 'vote_'; |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * @see parent::getFieldTypes |
| 78 | + * |
| 79 | + * @since 0.1 |
| 80 | + * |
| 81 | + * @return array |
| 82 | + */ |
| 83 | + protected function getFieldTypes() { |
| 84 | + return array( |
| 85 | + 'id' => 'id', |
| 86 | + 'contestant_id' => 'id', |
| 87 | + 'user_id' => 'id', |
| 88 | + |
| 89 | + 'value' => 'int', |
| 90 | + ); |
| 91 | + } |
| 92 | + |
| 93 | + /** |
| 94 | + * @see parent::getDefaults |
| 95 | + * |
| 96 | + * @since 0.1 |
| 97 | + * |
| 98 | + * @return array |
| 99 | + */ |
| 100 | + public function getDefaults() { |
| 101 | + return array( |
| 102 | + ); |
| 103 | + } |
| 104 | + |
| 105 | + /** |
| 106 | + * (non-PHPdoc) |
| 107 | + * @see ContestDBObject::writeToDB() |
| 108 | + * @return bool |
| 109 | + */ |
| 110 | + public function writeToDB() { |
| 111 | + $success = parent::writeToDB(); |
| 112 | + |
| 113 | + if ( $success ) { |
| 114 | + $contestant = new ContestContestant( array( 'id' => $this->getField( 'contestant_id' ) ) ); |
| 115 | + $contestant->updateVotes(); |
| 116 | + $contestant->writeToDB(); |
| 117 | + } |
| 118 | + |
| 119 | + return $success; |
| 120 | + } |
| 121 | + |
| 122 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestVote.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 123 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestReminderJob.php |
— | — | @@ -0,0 +1,45 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contest reminder job for email reminders. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ContestReminderJob.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class ContestReminderJob extends Job { |
| 16 | + |
| 17 | + /** |
| 18 | + * Constructor. |
| 19 | + * |
| 20 | + * @param Title $title |
| 21 | + * @param array $params |
| 22 | + * * contestants, array of ContestContestant, required |
| 23 | + * * contest, Contest, required |
| 24 | + */ |
| 25 | + public function __construct( Title $title, array $params ) { |
| 26 | + parent::__construct( 'ContestReminderJob', $title, $params ); |
| 27 | + $this->params['emailText'] = ContestUtils::getParsedArticleContent( $this->params['reminder_email'] ); |
| 28 | + $this->params['daysLeft'] = $this->params['contest']->getDaysLeft(); |
| 29 | + } |
| 30 | + |
| 31 | + /** |
| 32 | + * Execute the job. |
| 33 | + * |
| 34 | + * @return bool |
| 35 | + */ |
| 36 | + public function run() { |
| 37 | + foreach ( $this->params['contestants'] as /* ContestContestant */ $contestant ) { |
| 38 | + $contestant->sendReminderEmail( $this->params['emailText'], array( |
| 39 | + 'daysLeft' => $this->params['daysLeft'], |
| 40 | + ) ); |
| 41 | + } |
| 42 | + |
| 43 | + return true; |
| 44 | + } |
| 45 | + |
| 46 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestReminderJob.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 47 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestUtils.php |
— | — | @@ -0,0 +1,71 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Utility functions for the Contest extension. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ContestUtils.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class ContestUtils { |
| 16 | + |
| 17 | + /** |
| 18 | + * Gets the content of the article with the provided page name, |
| 19 | + * or an empty string when there is no such article. |
| 20 | + * |
| 21 | + * @since 0.1 |
| 22 | + * |
| 23 | + * @param string $pageName |
| 24 | + * |
| 25 | + * @return string |
| 26 | + */ |
| 27 | + public static function getArticleContent( $pageName ) { |
| 28 | + $title = Title::newFromText( $pageName ); |
| 29 | + |
| 30 | + if ( is_null( $title ) ) { |
| 31 | + return ''; |
| 32 | + } |
| 33 | + |
| 34 | + $article = new Article( $title, 0 ); |
| 35 | + return $article->fetchContent(); |
| 36 | + } |
| 37 | + |
| 38 | + /** |
| 39 | + * Gets the content of the article with the provided page name, |
| 40 | + * or an empty string when there is no such article. |
| 41 | + * |
| 42 | + * @since 0.1 |
| 43 | + * |
| 44 | + * @param string $pageName |
| 45 | + * |
| 46 | + * @return string |
| 47 | + */ |
| 48 | + public static function getParsedArticleContent( $pageName ) { |
| 49 | + $title = Title::newFromText( $pageName ); |
| 50 | + |
| 51 | + if ( is_null( $title ) ) { |
| 52 | + return ''; |
| 53 | + } |
| 54 | + |
| 55 | + $article = new Article( $title, 0 ); |
| 56 | + |
| 57 | + global $wgParser, $wgContestEmailParse; |
| 58 | + |
| 59 | + $wgContestEmailParse = true; |
| 60 | + |
| 61 | + $text = $wgParser->parse( |
| 62 | + $article->fetchContent(), |
| 63 | + $article->getTitle(), |
| 64 | + $article->getParserOptions() |
| 65 | + )->getText(); |
| 66 | + |
| 67 | + $wgContestEmailParse = false; |
| 68 | + |
| 69 | + return $text; |
| 70 | + } |
| 71 | + |
| 72 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestUtils.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 73 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestantPager.php |
— | — | @@ -0,0 +1,283 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contestant pager, for on Special:Contest |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ContestantPager.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class ContestantPager extends TablePager { |
| 16 | + |
| 17 | + /** |
| 18 | + * Query conditions, full field names (ie inc prefix). |
| 19 | + * @var array |
| 20 | + */ |
| 21 | + protected $conds; |
| 22 | + |
| 23 | + /** |
| 24 | + * Special page on which the pager is displayed. |
| 25 | + * @var SpecialContestPage |
| 26 | + */ |
| 27 | + protected $page; |
| 28 | + |
| 29 | + /** |
| 30 | + * Cache for challenge titles. |
| 31 | + * challenge id => challenge title |
| 32 | + * @var array |
| 33 | + */ |
| 34 | + protected $challengeTitles = array(); |
| 35 | + |
| 36 | + /** |
| 37 | + * Constructor. |
| 38 | + * |
| 39 | + * @param SpecialContestPage $page |
| 40 | + * @param array $conds |
| 41 | + */ |
| 42 | + public function __construct( SpecialContestPage $page, array $conds ) { |
| 43 | + $this->page = $page; |
| 44 | + $this->conds = $conds; |
| 45 | + $this->mDefaultDirection = true; |
| 46 | + |
| 47 | + $this->queryChallengeTitles( $conds ); |
| 48 | + |
| 49 | + // when MW 1.19 becomes min, we want to pass an IContextSource $context here. |
| 50 | + parent::__construct(); |
| 51 | + |
| 52 | + $this->getOutput()->addModules( 'contest.contestant.pager' ); |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * Query all challenge names we might need, |
| 57 | + * based on the queries conditions, and set them |
| 58 | + * to the challengeTitles field. |
| 59 | + * |
| 60 | + * @since 0.1 |
| 61 | + * |
| 62 | + * @param array $allConds |
| 63 | + */ |
| 64 | + protected function queryChallengeTitles( array $allConds ) { |
| 65 | + $conds = array(); |
| 66 | + |
| 67 | + if ( array_key_exists( 'contestant_contest_id', $allConds ) ) { |
| 68 | + $conds['contest_id'] = $allConds['contestant_contest_id']; |
| 69 | + } |
| 70 | + |
| 71 | + if ( array_key_exists( 'contestant_challenge_id', $allConds ) ) { |
| 72 | + $conds['id'] = $allConds['contestant_challenge_id']; |
| 73 | + } |
| 74 | + |
| 75 | + foreach ( ContestChallenge::s()->select( array( 'id', 'title' ), $conds ) as /* ContestChallenge */ $challenge ) { |
| 76 | + $this->challengeTitles[$challenge->getId()] = $challenge->getField( 'title' ); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * Gets the title of a challenge given it's id. |
| 82 | + * |
| 83 | + * @since 0.1 |
| 84 | + * |
| 85 | + * @param integer $challengeId |
| 86 | + * @throws MWException |
| 87 | + */ |
| 88 | + protected function getChallengeTitle( $challengeId ) { |
| 89 | + if ( array_key_exists( $challengeId, $this->challengeTitles ) ) { |
| 90 | + return $this->challengeTitles[$challengeId]; |
| 91 | + } |
| 92 | + else { |
| 93 | + throw new MWException( 'Attempt to get non-set challenge title' ); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * Get the OutputPage being used for this instance. |
| 99 | + * IndexPager extends ContextSource as of 1.19. |
| 100 | + * |
| 101 | + * @since 0.1 |
| 102 | + * |
| 103 | + * @return OutputPage |
| 104 | + */ |
| 105 | + public function getOutput() { |
| 106 | + return version_compare( $GLOBALS['wgVersion'], '1.18', '>' ) ? parent::getOutput() : $GLOBALS['wgOut']; |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Get the Language being used for this instance. |
| 111 | + * IndexPager extends ContextSource as of 1.19. |
| 112 | + * |
| 113 | + * @since 0.1 |
| 114 | + * |
| 115 | + * @return Language |
| 116 | + */ |
| 117 | + public function getLang() { |
| 118 | + return version_compare( $GLOBALS['wgVersion'], '1.18', '>' ) ? parent::getLang() : $GLOBALS['wgLang']; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * @return array |
| 123 | + */ |
| 124 | + public function getFieldNames() { |
| 125 | + static $headers = null; |
| 126 | + |
| 127 | + if ( is_null( $headers ) ) { |
| 128 | + $headers = array( |
| 129 | + 'contestant_id' => 'contest-contestant-id', |
| 130 | + 'contestant_challenge_id' => 'contest-contestant-challenge-name', |
| 131 | + 'contestant_volunteer' => 'contest-contestant-volunteer', |
| 132 | + 'contestant_wmf' => 'contest-contestant-wmf', |
| 133 | + 'contestant_comments' => 'contest-contestant-commentcount', |
| 134 | + 'contestant_rating' => 'contest-contestant-overallrating', |
| 135 | + ); |
| 136 | + |
| 137 | + $headers = array_map( 'wfMsg', $headers ); |
| 138 | + } |
| 139 | + |
| 140 | + return $headers; |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * @param $row |
| 145 | + * @return string |
| 146 | + */ |
| 147 | + function formatRow( $row ) { |
| 148 | + $this->mCurrentRow = $row; # In case formatValue etc need to know |
| 149 | + $s = Xml::openElement( 'tr', $this->getRowAttrs($row) ); |
| 150 | + |
| 151 | + foreach ( $this->getFieldNames() as $field => $name ) { |
| 152 | + $value = isset( $row->$field ) ? $row->$field : null; |
| 153 | + $formatted = strval( $this->formatValue( $field, $value ) ); |
| 154 | + |
| 155 | + if ( $formatted == '' ) { |
| 156 | + $formatted = ' '; |
| 157 | + } |
| 158 | + $s .= Xml::tags( 'td', $this->getCellAttrs( $field, $value ), $formatted ); |
| 159 | + } |
| 160 | + $s .= "</tr>\n"; |
| 161 | + |
| 162 | + return $s; |
| 163 | + } |
| 164 | + |
| 165 | + /** |
| 166 | + * @param $row |
| 167 | + * @return array |
| 168 | + */ |
| 169 | + function getRowAttrs( $row ) { |
| 170 | + return array_merge( |
| 171 | + parent::getRowAttrs( $row ), |
| 172 | + array( 'data-contestant-target' => SpecialPage::getTitleFor( 'Contestant', $row->contestant_id )->getLocalURL() ) |
| 173 | + ); |
| 174 | + } |
| 175 | + |
| 176 | + /** |
| 177 | + * @param $row |
| 178 | + * @return string |
| 179 | + */ |
| 180 | + function getRowClass( $row ) { |
| 181 | + return 'contestant-row'; |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * @param $name |
| 186 | + * @param $value |
| 187 | + * @return string |
| 188 | + */ |
| 189 | + public function formatValue( $name, $value ) { |
| 190 | + switch ( $name ) { |
| 191 | + case 'contestant_id': |
| 192 | + $value = Html::element( |
| 193 | + 'a', |
| 194 | + array( |
| 195 | + 'href' => SpecialPage::getTitleFor( 'Contestant', $value )->getLocalURL() |
| 196 | + ), |
| 197 | + $value |
| 198 | + ); |
| 199 | + break; |
| 200 | + case 'contestant_challenge_id': |
| 201 | + $value = Html::element( |
| 202 | + 'a', |
| 203 | + array( |
| 204 | + 'href' => |
| 205 | + SpecialPage::getTitleFor( |
| 206 | + 'Contest', |
| 207 | + $this->page->subPage . '/' . $this->getChallengeTitle( $value ) |
| 208 | + )->getLocalURL() |
| 209 | + ), |
| 210 | + $this->getChallengeTitle( $value ) |
| 211 | + ); |
| 212 | + break; |
| 213 | + case 'contestant_volunteer': case 'contestant_wmf': |
| 214 | + // contest-contestant-yes, contest-contestant-no |
| 215 | + $value = htmlspecialchars( wfMsg( 'contest-contestant-' . ( $value === '1' ? 'yes' : 'no' ) ) ); |
| 216 | + break; |
| 217 | + case 'contestant_comments': |
| 218 | + $value = htmlspecialchars( $this->getLang()->formatNum( $value ) ); |
| 219 | + break; |
| 220 | + case 'contestant_rating': |
| 221 | + $value = htmlspecialchars( wfMsgExt( |
| 222 | + 'contest-contestant-rating', |
| 223 | + 'parsemag', |
| 224 | + $this->getLang()->formatNum( $value ), |
| 225 | + $this->getLang()->formatNum( $this->mCurrentRow->contestant_rating_count ) |
| 226 | + ) ); |
| 227 | + break; |
| 228 | + } |
| 229 | + |
| 230 | + return $value; |
| 231 | + } |
| 232 | + |
| 233 | + function getQueryInfo() { |
| 234 | + $info = array( |
| 235 | + 'tables' => array( 'contest_contestants' ), |
| 236 | + 'fields' => array( |
| 237 | + 'contestant_id', |
| 238 | + 'contestant_challenge_id', |
| 239 | + 'contestant_volunteer', |
| 240 | + 'contestant_wmf', |
| 241 | + 'contestant_comments', |
| 242 | + 'contestant_rating', |
| 243 | + 'contestant_rating_count', |
| 244 | + ), |
| 245 | + 'conds' => $this->conds, |
| 246 | + ); |
| 247 | + |
| 248 | + return $info; |
| 249 | + } |
| 250 | + |
| 251 | + public function getTableClass(){ |
| 252 | + return 'TablePager contest-contestants'; |
| 253 | + } |
| 254 | + |
| 255 | + function getIndexField() { |
| 256 | + return 'contestant_id'; |
| 257 | + } |
| 258 | + |
| 259 | + function getDefaultSort() { |
| 260 | + return 'contestant_id'; |
| 261 | + } |
| 262 | + |
| 263 | + function isFieldSortable( $name ) { |
| 264 | + return in_array( |
| 265 | + $name, |
| 266 | + array( |
| 267 | + 'contestant_id', |
| 268 | + 'contestant_challenge_id', |
| 269 | + 'contestant_volunteer', |
| 270 | + 'contestant_wmf', |
| 271 | + 'contestant_comments', |
| 272 | + 'contestant_rating', |
| 273 | + ) |
| 274 | + ); |
| 275 | + } |
| 276 | + |
| 277 | + /** |
| 278 | + * @return Title |
| 279 | + */ |
| 280 | + function getTitle() { |
| 281 | + return $this->page->getFullTitle(); |
| 282 | + } |
| 283 | + |
| 284 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestantPager.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 285 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestChallenge.php |
— | — | @@ -0,0 +1,126 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class representing a single contest challenge object. |
| 6 | + * Each contest (can) has a list of associated challenges. |
| 7 | + * |
| 8 | + * @since 0.1 |
| 9 | + * |
| 10 | + * @file ContestChallenge.php |
| 11 | + * @ingroup Contest |
| 12 | + * |
| 13 | + * @licence GNU GPL v3 or later |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ContestChallenge extends ContestDBObject { |
| 17 | + |
| 18 | + /** |
| 19 | + * Method to get an instance so methods that ought to be static, |
| 20 | + * but can't be due to PHP 5.2 not having LSB, can be called on |
| 21 | + * it. This also allows easy identifying of code that needs to |
| 22 | + * be changed once PHP 5.3 becomes an acceptable requirement. |
| 23 | + * |
| 24 | + * @since 0.1 |
| 25 | + * |
| 26 | + * @return ContestDBObject |
| 27 | + */ |
| 28 | + public static function s() { |
| 29 | + static $instance = false; |
| 30 | + |
| 31 | + if ( $instance === false ) { |
| 32 | + $instance = new self( array() ); |
| 33 | + } |
| 34 | + |
| 35 | + return $instance; |
| 36 | + } |
| 37 | + |
| 38 | + /** |
| 39 | + * Get a new instance of the class from an array. |
| 40 | + * This method ought to be in the basic class and |
| 41 | + * return a new static(), but this requires LSB/PHP>=5.3. |
| 42 | + * |
| 43 | + * @since 0.1 |
| 44 | + * |
| 45 | + * @param array $data |
| 46 | + * @param boolean $loadDefaults |
| 47 | + * |
| 48 | + * @return ContestDBObject |
| 49 | + */ |
| 50 | + public function newFromArray( array $data, $loadDefaults = false ) { |
| 51 | + return new self( $data, $loadDefaults ); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * @see parent::getFieldTypes |
| 56 | + * |
| 57 | + * @since 0.1 |
| 58 | + * |
| 59 | + * @return string |
| 60 | + */ |
| 61 | + public function getDBTable() { |
| 62 | + return 'contest_challenges'; |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * @see parent::getFieldTypes |
| 67 | + * |
| 68 | + * @since 0.1 |
| 69 | + * |
| 70 | + * @return string |
| 71 | + */ |
| 72 | + protected function getFieldPrefix() { |
| 73 | + return 'challenge_'; |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * @see parent::getFieldTypes |
| 78 | + * |
| 79 | + * @since 0.1 |
| 80 | + * |
| 81 | + * @return array |
| 82 | + */ |
| 83 | + protected function getFieldTypes() { |
| 84 | + return array( |
| 85 | + 'id' => 'id', |
| 86 | + 'contest_id' => 'id', |
| 87 | + 'text' => 'str', |
| 88 | + 'title' => 'str', |
| 89 | + 'oneline' => 'str', |
| 90 | + ); |
| 91 | + } |
| 92 | + |
| 93 | + /** |
| 94 | + * @see parent::getDefaults |
| 95 | + * |
| 96 | + * @since 0.1 |
| 97 | + * |
| 98 | + * @return array |
| 99 | + */ |
| 100 | + public function getDefaults() { |
| 101 | + return array( |
| 102 | + 'text' => '', |
| 103 | + 'title' => '', |
| 104 | + 'oneline' => '', |
| 105 | + ); |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * Returns an array with challenge IDs (keys) and their associated titles (values) |
| 110 | + * for the provided list of IDs. |
| 111 | + * |
| 112 | + * @param array|integer $ids |
| 113 | + * |
| 114 | + * @return array( id => title ) |
| 115 | + */ |
| 116 | + public static function getTitlesForIds( $ids ) { |
| 117 | + $challenges = self::s()->select( array( 'id', 'title' ), array( 'id' => $ids ) ); |
| 118 | + $results = array(); |
| 119 | + |
| 120 | + foreach ( $challenges as /* ContestChallenge */ $challenge ) { |
| 121 | + $results[$challenge->getId()] = $challenge->getField( 'title' ); |
| 122 | + } |
| 123 | + |
| 124 | + return $results; |
| 125 | + } |
| 126 | + |
| 127 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestChallenge.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 128 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestComment.php |
— | — | @@ -0,0 +1,123 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class representing a single contest comment. |
| 6 | + * Comments can be made by judges on (submissions of) contestants. |
| 7 | + * |
| 8 | + * @since 0.1 |
| 9 | + * |
| 10 | + * @file ContestComment.php |
| 11 | + * @ingroup Contest |
| 12 | + * |
| 13 | + * @licence GNU GPL v3 or later |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ContestComment extends ContestDBObject { |
| 17 | + |
| 18 | + /** |
| 19 | + * Method to get an instance so methods that ought to be static, |
| 20 | + * but can't be due to PHP 5.2 not having LSB, can be called on |
| 21 | + * it. This also allows easy identifying of code that needs to |
| 22 | + * be changed once PHP 5.3 becomes an acceptable requirement. |
| 23 | + * |
| 24 | + * @since 0.1 |
| 25 | + * |
| 26 | + * @return ContestDBObject |
| 27 | + */ |
| 28 | + public static function s() { |
| 29 | + static $instance = false; |
| 30 | + |
| 31 | + if ( $instance === false ) { |
| 32 | + $instance = new self( array() ); |
| 33 | + } |
| 34 | + |
| 35 | + return $instance; |
| 36 | + } |
| 37 | + |
| 38 | + /** |
| 39 | + * Get a new instance of the class from an array. |
| 40 | + * This method ought to be in the basic class and |
| 41 | + * return a new static(), but this requires LSB/PHP>=5.3. |
| 42 | + * |
| 43 | + * @since 0.1 |
| 44 | + * |
| 45 | + * @param array $data |
| 46 | + * @param boolean $loadDefaults |
| 47 | + * |
| 48 | + * @return ContestDBObject |
| 49 | + */ |
| 50 | + public function newFromArray( array $data, $loadDefaults = false ) { |
| 51 | + return new self( $data, $loadDefaults ); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * @see parent::getFieldTypes |
| 56 | + * |
| 57 | + * @since 0.1 |
| 58 | + * |
| 59 | + * @return string |
| 60 | + */ |
| 61 | + public function getDBTable() { |
| 62 | + return 'contest_comments'; |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * @see parent::getFieldTypes |
| 67 | + * |
| 68 | + * @since 0.1 |
| 69 | + * |
| 70 | + * @return string |
| 71 | + */ |
| 72 | + protected function getFieldPrefix() { |
| 73 | + return 'comment_'; |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * @see parent::getFieldTypes |
| 78 | + * |
| 79 | + * @since 0.1 |
| 80 | + * |
| 81 | + * @return array |
| 82 | + */ |
| 83 | + protected function getFieldTypes() { |
| 84 | + return array( |
| 85 | + 'id' => 'id', |
| 86 | + 'contestant_id' => 'id', |
| 87 | + 'user_id' => 'id', |
| 88 | + |
| 89 | + 'text' => 'str', |
| 90 | + 'time' => 'int', |
| 91 | + ); |
| 92 | + } |
| 93 | + |
| 94 | + /** |
| 95 | + * @see parent::getDefaults |
| 96 | + * |
| 97 | + * @since 0.1 |
| 98 | + * |
| 99 | + * @return array |
| 100 | + */ |
| 101 | + public function getDefaults() { |
| 102 | + return array( |
| 103 | + 'text' => '', |
| 104 | + 'time' => 0, |
| 105 | + ); |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * (non-PHPdoc) |
| 110 | + * @see ContestDBObject::insertIntoDB() |
| 111 | + * @return bool |
| 112 | + */ |
| 113 | + protected function insertIntoDB() { |
| 114 | + $success = parent::insertIntoDB(); |
| 115 | + |
| 116 | + if ( $success ) { |
| 117 | + $contestant = new ContestContestant( array( 'id' => $this->getField( 'contestant_id' ) ) ); |
| 118 | + $contestant->addToField( 'comments', 1 ); |
| 119 | + } |
| 120 | + |
| 121 | + return $success; |
| 122 | + } |
| 123 | + |
| 124 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestComment.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 125 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestContestant.php |
— | — | @@ -0,0 +1,602 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class representing a single contest contestant. |
| 6 | + * A contestant is a unique user + contest combination. |
| 7 | + * |
| 8 | + * @since 0.1 |
| 9 | + * |
| 10 | + * @file ContestContestant.php |
| 11 | + * @ingroup Contest |
| 12 | + * |
| 13 | + * @licence GNU GPL v3 or later |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ContestContestant extends ContestDBObject { |
| 17 | + |
| 18 | + protected $contest = null; |
| 19 | + |
| 20 | + /** |
| 21 | + * Cached user object, created from the user_id field. |
| 22 | + * |
| 23 | + * @since 0.1 |
| 24 | + * @var USer |
| 25 | + */ |
| 26 | + protected $user = null; |
| 27 | + |
| 28 | + /** |
| 29 | + * Method to get an instance so methods that ought to be static, |
| 30 | + * but can't be due to PHP 5.2 not having LSB, can be called on |
| 31 | + * it. This also allows easy identifying of code that needs to |
| 32 | + * be changed once PHP 5.3 becomes an acceptable requirement. |
| 33 | + * |
| 34 | + * @since 0.1 |
| 35 | + * |
| 36 | + * @return ContestDBObject |
| 37 | + */ |
| 38 | + public static function s() { |
| 39 | + static $instance = false; |
| 40 | + |
| 41 | + if ( $instance === false ) { |
| 42 | + $instance = new self( array() ); |
| 43 | + } |
| 44 | + |
| 45 | + return $instance; |
| 46 | + } |
| 47 | + |
| 48 | + /** |
| 49 | + * Get a new instance of the class from an array. |
| 50 | + * This method ought to be in the basic class and |
| 51 | + * return a new static(), but this requires LSB/PHP>=5.3. |
| 52 | + * |
| 53 | + * @since 0.1 |
| 54 | + * |
| 55 | + * @param array $data |
| 56 | + * @param boolean $loadDefaults |
| 57 | + * |
| 58 | + * @return ContestDBObject |
| 59 | + */ |
| 60 | + public function newFromArray( array $data, $loadDefaults = false ) { |
| 61 | + return new self( $data, $loadDefaults ); |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * @see parent::getFieldTypes |
| 66 | + * |
| 67 | + * @since 0.1 |
| 68 | + * |
| 69 | + * @return string |
| 70 | + */ |
| 71 | + public function getDBTable() { |
| 72 | + return 'contest_contestants'; |
| 73 | + } |
| 74 | + |
| 75 | + /** |
| 76 | + * @see parent::getFieldTypes |
| 77 | + * |
| 78 | + * @since 0.1 |
| 79 | + * |
| 80 | + * @return string |
| 81 | + */ |
| 82 | + protected function getFieldPrefix() { |
| 83 | + return 'contestant_'; |
| 84 | + } |
| 85 | + |
| 86 | + /** |
| 87 | + * @see parent::getFieldTypes |
| 88 | + * |
| 89 | + * @since 0.1 |
| 90 | + * |
| 91 | + * @return array |
| 92 | + */ |
| 93 | + protected function getFieldTypes() { |
| 94 | + return array( |
| 95 | + 'id' => 'id', |
| 96 | + 'contest_id' => 'id', |
| 97 | + 'challenge_id' => 'id', |
| 98 | + 'user_id' => 'id', |
| 99 | + |
| 100 | + 'full_name' => 'str', |
| 101 | + 'user_name' => 'str', |
| 102 | + 'email' => 'str', |
| 103 | + |
| 104 | + 'country' => 'str', |
| 105 | + 'volunteer' => 'bool', |
| 106 | + 'wmf' => 'bool', |
| 107 | + 'cv' => 'str', |
| 108 | + |
| 109 | + 'submission' => 'str', |
| 110 | + |
| 111 | + 'rating' => 'float', |
| 112 | + 'rating_count' => 'int', |
| 113 | + 'comments' => 'int', |
| 114 | + ); |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * @see parent::getDefaults |
| 119 | + * |
| 120 | + * @since 0.1 |
| 121 | + * |
| 122 | + * @return array |
| 123 | + */ |
| 124 | + public function getDefaults() { |
| 125 | + return array( |
| 126 | + 'full_name' => '', |
| 127 | + 'user_name' => '', |
| 128 | + 'email' => '', |
| 129 | + |
| 130 | + 'country' => '', |
| 131 | + 'volunteer' => false, |
| 132 | + 'wmf' => false, |
| 133 | + 'cv' => false, |
| 134 | + |
| 135 | + 'submission' => '', |
| 136 | + |
| 137 | + 'rating' => 0, |
| 138 | + 'rating_count' => 0, |
| 139 | + 'comments' => 0, |
| 140 | + ); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * Gets the contest for this participant. |
| 145 | + * |
| 146 | + * @since 0.1 |
| 147 | + * |
| 148 | + * @param array|string|null $fields The fields to load, null for all fields. |
| 149 | + * |
| 150 | + * @return Contest |
| 151 | + */ |
| 152 | + public function getContest( $fields = null ) { |
| 153 | + if ( !is_null( $this->contest ) ) { |
| 154 | + return $this->contest; |
| 155 | + } |
| 156 | + |
| 157 | + $contest = Contest::s()->selectRow( $fields, array( 'id' => $this->getField( 'contest_id' ) ) ); |
| 158 | + |
| 159 | + if ( is_null( $this->contest ) && is_null( $fields ) ) { |
| 160 | + $this->contest = $contest; |
| 161 | + } |
| 162 | + |
| 163 | + return $contest; |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Sets the contest for this participant. |
| 168 | + * |
| 169 | + * @since 0.1 |
| 170 | + * |
| 171 | + * @param Contest $contest |
| 172 | + */ |
| 173 | + public function setContest( Contest $contest ) { |
| 174 | + $this->contest = $contest; |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * Returns a list of countries and their corresponding country |
| 179 | + * codes that can be fed directly into an HTML input. |
| 180 | + * |
| 181 | + * @since 0.1 |
| 182 | + * |
| 183 | + * @param booolean $addEmptyItem |
| 184 | + * |
| 185 | + * @return array |
| 186 | + */ |
| 187 | + public static function getCountriesForInput( $addEmptyItem = false ) { |
| 188 | + $countries = array(); |
| 189 | + |
| 190 | + if ( $addEmptyItem ) { |
| 191 | + $countries[''] = ''; |
| 192 | + } |
| 193 | + |
| 194 | + foreach ( self::getCountries() as $code => $name ) { |
| 195 | + $countries["$code - $name"] = $code; |
| 196 | + } |
| 197 | + |
| 198 | + return $countries; |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Returns a list of ISO 3166-1-alpha-2 country codes (keys) and their corresponding country (values). |
| 203 | + * |
| 204 | + * @since 0.1 |
| 205 | + * |
| 206 | + * @return array |
| 207 | + */ |
| 208 | + public static function getCountries() { |
| 209 | + return array( |
| 210 | + 'AF' => 'Afghanistan', |
| 211 | + 'AL' => 'Albania', |
| 212 | + 'DZ' => 'Algeria', |
| 213 | + 'AS' => 'American Samoa', |
| 214 | + 'AD' => 'Andorra', |
| 215 | + 'AO' => 'Angola', |
| 216 | + 'AI' => 'Anguilla', |
| 217 | + 'AQ' => 'Antarctica', |
| 218 | + 'AG' => 'Antigua and Barbuda', |
| 219 | + 'AR' => 'Argentina', |
| 220 | + 'AM' => 'Armenia', |
| 221 | + 'AW' => 'Aruba', |
| 222 | + 'AU' => 'Australia', |
| 223 | + 'AT' => 'Austria', |
| 224 | + 'AZ' => 'Azerbaijan', |
| 225 | + 'BS' => 'Bahamas', |
| 226 | + 'BH' => 'Bahrain', |
| 227 | + 'BD' => 'Bangladesh', |
| 228 | + 'BB' => 'Barbados', |
| 229 | + 'BY' => 'Belarus', |
| 230 | + 'BE' => 'Belgium', |
| 231 | + 'BZ' => 'Belize', |
| 232 | + 'BJ' => 'Benin', |
| 233 | + 'BM' => 'Bermuda', |
| 234 | + 'BT' => 'Bhutan', |
| 235 | + 'BO' => 'Bolivia', |
| 236 | + 'BA' => 'Bosnia and Herzegovina', |
| 237 | + 'BW' => 'Botswana', |
| 238 | + 'BV' => 'Bouvet Island', |
| 239 | + 'BR' => 'Brazil', |
| 240 | + 'IO' => 'British Indian Ocean Territory', |
| 241 | + 'BN' => 'Brunei Darussalam', |
| 242 | + 'BG' => 'Bulgaria', |
| 243 | + 'BF' => 'Burkina Faso', |
| 244 | + 'BI' => 'Burundi', |
| 245 | + 'KH' => 'Cambodia', |
| 246 | + 'CM' => 'Cameroon', |
| 247 | + 'CA' => 'Canada', |
| 248 | + 'CV' => 'Cape Verde', |
| 249 | + 'KY' => 'Cayman Islands', |
| 250 | + 'CF' => 'Central African Republic', |
| 251 | + 'TD' => 'Chad', |
| 252 | + 'CL' => 'Chile', |
| 253 | + 'CN' => 'China', |
| 254 | + 'CX' => 'Christmas Island', |
| 255 | + 'CC' => 'Cocos (Keeling) Islands', |
| 256 | + 'CO' => 'Colombia', |
| 257 | + 'KM' => 'Comoros', |
| 258 | + 'CG' => 'Congo', |
| 259 | + 'CD' => 'Congo, the Democratic Republic of the', |
| 260 | + 'CK' => 'Cook Islands', |
| 261 | + 'CR' => 'Costa Rica', |
| 262 | + 'CI' => "Cote D'Ivoire", |
| 263 | + 'HR' => 'Croatia', |
| 264 | + 'CU' => 'Cuba', |
| 265 | + 'CY' => 'Cyprus', |
| 266 | + 'CZ' => 'Czech Republic', |
| 267 | + 'DK' => 'Denmark', |
| 268 | + 'DJ' => 'Djibouti', |
| 269 | + 'DM' => 'Dominica', |
| 270 | + 'DO' => 'Dominican Republic', |
| 271 | + 'EC' => 'Ecuador', |
| 272 | + 'EG' => 'Egypt', |
| 273 | + 'SV' => 'El Salvador', |
| 274 | + 'GQ' => 'Equatorial Guinea', |
| 275 | + 'ER' => 'Eritrea', |
| 276 | + 'EE' => 'Estonia', |
| 277 | + 'ET' => 'Ethiopia', |
| 278 | + 'FK' => 'Falkland Islands (Malvinas)', |
| 279 | + 'FO' => 'Faroe Islands', |
| 280 | + 'FJ' => 'Fiji', |
| 281 | + 'FI' => 'Finland', |
| 282 | + 'FR' => 'France', |
| 283 | + 'GF' => 'French Guiana', |
| 284 | + 'PF' => 'French Polynesia', |
| 285 | + 'TF' => 'French Southern Territories', |
| 286 | + 'GA' => 'Gabon', |
| 287 | + 'GM' => 'Gambia', |
| 288 | + 'GE' => 'Georgia', |
| 289 | + 'DE' => 'Germany', |
| 290 | + 'GH' => 'Ghana', |
| 291 | + 'GI' => 'Gibraltar', |
| 292 | + 'GR' => 'Greece', |
| 293 | + 'GL' => 'Greenland', |
| 294 | + 'GD' => 'Grenada', |
| 295 | + 'GP' => 'Guadeloupe', |
| 296 | + 'GU' => 'Guam', |
| 297 | + 'GT' => 'Guatemala', |
| 298 | + 'GN' => 'Guinea', |
| 299 | + 'GW' => 'Guinea-Bissau', |
| 300 | + 'GY' => 'Guyana', |
| 301 | + 'HT' => 'Haiti', |
| 302 | + 'HM' => 'Heard Island and Mcdonald Islands', |
| 303 | + 'VA' => 'Holy See (Vatican City State)', |
| 304 | + 'HN' => 'Honduras', |
| 305 | + 'HK' => 'Hong Kong', |
| 306 | + 'HU' => 'Hungary', |
| 307 | + 'IS' => 'Iceland', |
| 308 | + 'IN' => 'India', |
| 309 | + 'ID' => 'Indonesia', |
| 310 | + 'IR' => 'Iran, Islamic Republic of', |
| 311 | + 'IQ' => 'Iraq', |
| 312 | + 'IE' => 'Ireland', |
| 313 | + 'IL' => 'Israel', |
| 314 | + 'IT' => 'Italy', |
| 315 | + 'JM' => 'Jamaica', |
| 316 | + 'JP' => 'Japan', |
| 317 | + 'JO' => 'Jordan', |
| 318 | + 'KZ' => 'Kazakhstan', |
| 319 | + 'KE' => 'Kenya', |
| 320 | + 'KI' => 'Kiribati', |
| 321 | + 'KP' => "Korea, Democratic People's Republic of", |
| 322 | + 'KR' => 'Korea, Republic of', |
| 323 | + 'KW' => 'Kuwait', |
| 324 | + 'KG' => 'Kyrgyzstan', |
| 325 | + 'LA' => "Lao People's Democratic Republic", |
| 326 | + 'LV' => 'Latvia', |
| 327 | + 'LB' => 'Lebanon', |
| 328 | + 'LS' => 'Lesotho', |
| 329 | + 'LR' => 'Liberia', |
| 330 | + 'LY' => 'Libyan Arab Jamahiriya', |
| 331 | + 'LI' => 'Liechtenstein', |
| 332 | + 'LT' => 'Lithuania', |
| 333 | + 'LU' => 'Luxembourg', |
| 334 | + 'MO' => 'Macao', |
| 335 | + 'MK' => 'Macedonia, the Former Yugoslav Republic of', |
| 336 | + 'MG' => 'Madagascar', |
| 337 | + 'MW' => 'Malawi', |
| 338 | + 'MY' => 'Malaysia', |
| 339 | + 'MV' => 'Maldives', |
| 340 | + 'ML' => 'Mali', |
| 341 | + 'MT' => 'Malta', |
| 342 | + 'MH' => 'Marshall Islands', |
| 343 | + 'MQ' => 'Martinique', |
| 344 | + 'MR' => 'Mauritania', |
| 345 | + 'MU' => 'Mauritius', |
| 346 | + 'YT' => 'Mayotte', |
| 347 | + 'MX' => 'Mexico', |
| 348 | + 'FM' => 'Micronesia, Federated States of', |
| 349 | + 'MD' => 'Moldova, Republic of', |
| 350 | + 'MC' => 'Monaco', |
| 351 | + 'MN' => 'Mongolia', |
| 352 | + 'MS' => 'Montserrat', |
| 353 | + 'MA' => 'Morocco', |
| 354 | + 'MZ' => 'Mozambique', |
| 355 | + 'MM' => 'Myanmar', |
| 356 | + 'NA' => 'Namibia', |
| 357 | + 'NR' => 'Nauru', |
| 358 | + 'NP' => 'Nepal', |
| 359 | + 'NL' => 'Netherlands', |
| 360 | + 'AN' => 'Netherlands Antilles', |
| 361 | + 'NC' => 'New Caledonia', |
| 362 | + 'NZ' => 'New Zealand', |
| 363 | + 'NI' => 'Nicaragua', |
| 364 | + 'NE' => 'Niger', |
| 365 | + 'NG' => 'Nigeria', |
| 366 | + 'NU' => 'Niue', |
| 367 | + 'NF' => 'Norfolk Island', |
| 368 | + 'MP' => 'Northern Mariana Islands', |
| 369 | + 'NO' => 'Norway', |
| 370 | + 'OM' => 'Oman', |
| 371 | + 'PK' => 'Pakistan', |
| 372 | + 'PW' => 'Palau', |
| 373 | + 'PS' => 'Palestinian Territory, Occupied', |
| 374 | + 'PA' => 'Panama', |
| 375 | + 'PG' => 'Papua New Guinea', |
| 376 | + 'PY' => 'Paraguay', |
| 377 | + 'PE' => 'Peru', |
| 378 | + 'PH' => 'Philippines', |
| 379 | + 'PN' => 'Pitcairn', |
| 380 | + 'PL' => 'Poland', |
| 381 | + 'PT' => 'Portugal', |
| 382 | + 'PR' => 'Puerto Rico', |
| 383 | + 'QA' => 'Qatar', |
| 384 | + 'RE' => 'Reunion', |
| 385 | + 'RO' => 'Romania', |
| 386 | + 'RU' => 'Russian Federation', |
| 387 | + 'RW' => 'Rwanda', |
| 388 | + 'SH' => 'Saint Helena', |
| 389 | + 'KN' => 'Saint Kitts and Nevis', |
| 390 | + 'LC' => 'Saint Lucia', |
| 391 | + 'PM' => 'Saint Pierre and Miquelon', |
| 392 | + 'VC' => 'Saint Vincent and the Grenadines', |
| 393 | + 'WS' => 'Samoa', |
| 394 | + 'SM' => 'San Marino', |
| 395 | + 'ST' => 'Sao Tome and Principe', |
| 396 | + 'SA' => 'Saudi Arabia', |
| 397 | + 'SN' => 'Senegal', |
| 398 | + 'CS' => 'Serbia and Montenegro', |
| 399 | + 'SC' => 'Seychelles', |
| 400 | + 'SL' => 'Sierra Leone', |
| 401 | + 'SG' => 'Singapore', |
| 402 | + 'SK' => 'Slovakia', |
| 403 | + 'SI' => 'Slovenia', |
| 404 | + 'SB' => 'Solomon Islands', |
| 405 | + 'SO' => 'Somalia', |
| 406 | + 'ZA' => 'South Africa', |
| 407 | + //'GS' => 'South Georgia and the South Sandwich Islands', |
| 408 | + 'ES' => 'Spain', |
| 409 | + 'LK' => 'Sri Lanka', |
| 410 | + 'SD' => 'Sudan', |
| 411 | + 'SR' => 'Suriname', |
| 412 | + 'SJ' => 'Svalbard and Jan Mayen', |
| 413 | + 'SZ' => 'Swaziland', |
| 414 | + 'SE' => 'Sweden', |
| 415 | + 'CH' => 'Switzerland', |
| 416 | + 'SY' => 'Syrian Arab Republic', |
| 417 | + 'TW' => 'Taiwan, Province of China', |
| 418 | + 'TJ' => 'Tajikistan', |
| 419 | + 'TZ' => 'Tanzania, United Republic of', |
| 420 | + 'TH' => 'Thailand', |
| 421 | + 'TL' => 'Timor-Leste', |
| 422 | + 'TG' => 'Togo', |
| 423 | + 'TK' => 'Tokelau', |
| 424 | + 'TO' => 'Tonga', |
| 425 | + 'TT' => 'Trinidad and Tobago', |
| 426 | + 'TN' => 'Tunisia', |
| 427 | + 'TR' => 'Turkey', |
| 428 | + 'TM' => 'Turkmenistan', |
| 429 | + 'TC' => 'Turks and Caicos Islands', |
| 430 | + 'TV' => 'Tuvalu', |
| 431 | + 'UG' => 'Uganda', |
| 432 | + 'UA' => 'Ukraine', |
| 433 | + 'AE' => 'United Arab Emirates', |
| 434 | + 'GB' => 'United Kingdom', |
| 435 | + 'US' => 'United States', |
| 436 | + 'UM' => 'United States Minor Outlying Islands', |
| 437 | + 'UY' => 'Uruguay', |
| 438 | + 'UZ' => 'Uzbekistan', |
| 439 | + 'VU' => 'Vanuatu', |
| 440 | + 'VE' => 'Venezuela', |
| 441 | + 'VN' => 'Viet Nam', |
| 442 | + 'VG' => 'Virgin Islands, British', |
| 443 | + 'VI' => 'Virgin Islands, U.s.', |
| 444 | + 'WF' => 'Wallis and Futuna', |
| 445 | + 'EH' => 'Western Sahara', |
| 446 | + 'YE' => 'Yemen', |
| 447 | + 'ZM' => 'Zambia', |
| 448 | + 'ZW' => 'Zimbabwe' |
| 449 | + ); |
| 450 | + } |
| 451 | + |
| 452 | + /** |
| 453 | + * (non-PHPdoc) |
| 454 | + * @see ContestDBObject::insertIntoDB() |
| 455 | + * @return bool |
| 456 | + */ |
| 457 | + protected function insertIntoDB() { |
| 458 | + wfRunHooks( 'ContestBeforeContestantInsert', array( &$this ) ); |
| 459 | + |
| 460 | + $success = parent::insertIntoDB(); |
| 461 | + |
| 462 | + if ( $success ) { |
| 463 | + $this->onUserSignup(); |
| 464 | + } |
| 465 | + |
| 466 | + return $success; |
| 467 | + } |
| 468 | + |
| 469 | + /** |
| 470 | + * Handles successfull user signup for a contest. |
| 471 | + * |
| 472 | + * @since 0.1 |
| 473 | + */ |
| 474 | + protected function onUserSignup() { |
| 475 | + $this->getContest( array( 'id' ) )->addToSubmissionCount( 1 ); |
| 476 | + |
| 477 | + $this->getUser()->setOption( 'contest_showtoplink', true ); |
| 478 | + $this->getUser()->saveSettings(); // TODO: can't we just save this single option instead of everything? |
| 479 | + |
| 480 | + $this->sendSignupEmail(); |
| 481 | + |
| 482 | + wfRunHooks( 'ContestAfterContestantInsert', array( &$this ) ); |
| 483 | + } |
| 484 | + |
| 485 | + /** |
| 486 | + * Send the signup email. |
| 487 | + * |
| 488 | + * @since 0.1 |
| 489 | + * |
| 490 | + * @return Status |
| 491 | + */ |
| 492 | + public function sendSignupEmail() { |
| 493 | + global $wgPasswordSender, $wgPasswordSenderName; |
| 494 | + |
| 495 | + $title = wfMsg( 'contest-email-signup-title' ); |
| 496 | + $emailText = ContestUtils::getParsedArticleContent( $this->getContest()->getField( 'signup_email' ) ); |
| 497 | + $user = $this->getUser(); |
| 498 | + $sender = $wgPasswordSender; |
| 499 | + $senderName = $wgPasswordSenderName; |
| 500 | + |
| 501 | + wfRunHooks( 'ContestBeforeSignupEmail', array( &$this, &$title, &$emailText, &$user, &$sender, &$senderName ) ); |
| 502 | + |
| 503 | + return UserMailer::send( |
| 504 | + new MailAddress( $user ), |
| 505 | + new MailAddress( $sender, $senderName ), |
| 506 | + $title, |
| 507 | + $emailText, |
| 508 | + null, |
| 509 | + 'text/html; charset=ISO-8859-1' |
| 510 | + ); |
| 511 | + } |
| 512 | + |
| 513 | + /** |
| 514 | + * Send a reminder email. |
| 515 | + * |
| 516 | + * @since 0.1 |
| 517 | + * |
| 518 | + * @return Status |
| 519 | + */ |
| 520 | + public function sendReminderEmail( $emailText, array $params = array() ) { |
| 521 | + global $wgPasswordSender, $wgPasswordSenderName; |
| 522 | + |
| 523 | + if ( !array_key_exists( 'daysLeft', $params ) ) { |
| 524 | + $params['daysLeft'] = $this->getContest()->getDaysLeft(); |
| 525 | + } |
| 526 | + |
| 527 | + $title = wfMsgExt( 'contest-email-reminder-title', 'parsemag', $params['daysLeft'] ); |
| 528 | + $user = $this->getUser(); |
| 529 | + $sender = $wgPasswordSender; |
| 530 | + $senderName = $wgPasswordSenderName; |
| 531 | + |
| 532 | + wfRunHooks( 'ContestBeforeReminderEmail', array( &$this, &$title, &$emailText, &$user, &$sender, &$senderName ) ); |
| 533 | + |
| 534 | + return UserMailer::send( |
| 535 | + new MailAddress( $user ), |
| 536 | + new MailAddress( $sender, $senderName ), |
| 537 | + $title, |
| 538 | + $emailText, |
| 539 | + null, |
| 540 | + 'text/html; charset=ISO-8859-1' |
| 541 | + ); |
| 542 | + } |
| 543 | + |
| 544 | + /** |
| 545 | + * Update the vote count and avarage vote fields. |
| 546 | + * This does not write the changes to the database, |
| 547 | + * if this is required, call writeToDB. |
| 548 | + * |
| 549 | + * @since 0.1 |
| 550 | + */ |
| 551 | + public function updateVotes() { |
| 552 | + $votes = $this->getVotes(); |
| 553 | + |
| 554 | + $amount = count( $votes ); |
| 555 | + $total = 0; |
| 556 | + |
| 557 | + foreach ( $votes as /* ContestVote */ $vote ) { |
| 558 | + $total += $vote->getField( 'value' ); |
| 559 | + } |
| 560 | + |
| 561 | + $this->setField( 'rating_count', $amount ); |
| 562 | + $this->setField( 'rating', $amount > 0 ? $total / $amount : 0 ); |
| 563 | + } |
| 564 | + |
| 565 | + /** |
| 566 | + * Returns the user object for this contestant, created |
| 567 | + * from the user_id field and cached in $this->user. |
| 568 | + * |
| 569 | + * @since 0.1 |
| 570 | + * |
| 571 | + * @return User |
| 572 | + */ |
| 573 | + public function getUser() { |
| 574 | + if ( is_null( $this->user ) ) { |
| 575 | + $this->user = User::newFromId( $this->getField( 'user_id' ) ); |
| 576 | + } |
| 577 | + |
| 578 | + return $this->user; |
| 579 | + } |
| 580 | + |
| 581 | + /** |
| 582 | + * Get the votes for this contestant. |
| 583 | + * |
| 584 | + * @since 0.1 |
| 585 | + * |
| 586 | + * @return array of ContestVote |
| 587 | + */ |
| 588 | + public function getVotes() { |
| 589 | + return ContestVote::s()->select( null, array( 'contestant_id' => $this->getId() ) ); |
| 590 | + } |
| 591 | + |
| 592 | + /** |
| 593 | + * Get the comments for this contestant. |
| 594 | + * |
| 595 | + * @since 0.1 |
| 596 | + * |
| 597 | + * @return array of ContestComment |
| 598 | + */ |
| 599 | + public function getComments() { |
| 600 | + return ContestComment::s()->select( null, array( 'contestant_id' => $this->getId() ) ); |
| 601 | + } |
| 602 | + |
| 603 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/ContestContestant.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 604 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/includes/Contest.class.php |
— | — | @@ -0,0 +1,483 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class representing a single contest. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file Contest.class.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class Contest extends ContestDBObject { |
| 16 | + |
| 17 | + // Constants representing the states a contest can have. |
| 18 | + const STATUS_DRAFT = 0; |
| 19 | + const STATUS_ACTIVE = 1; |
| 20 | + const STATUS_FINISHED = 2; // manually stopped by contest manager |
| 21 | + const STATUS_EXPIRED = 3; // past configured contest end date |
| 22 | + |
| 23 | + /** |
| 24 | + * List of challenges for this contest. |
| 25 | + * @see loadChallenges, setChallenges and writeChallengesToDB |
| 26 | + * |
| 27 | + * @since 0.1 |
| 28 | + * @var array of ContestChallenge |
| 29 | + */ |
| 30 | + protected $challenges = null; |
| 31 | + |
| 32 | + /** |
| 33 | + * List of contestants for this contest. |
| 34 | + * @see loadContestants, setContestants and writeContestantsToDB |
| 35 | + * |
| 36 | + * @since 0.1 |
| 37 | + * @var array of ContestContestant |
| 38 | + */ |
| 39 | + protected $contestants = null; |
| 40 | + |
| 41 | + /** |
| 42 | + * Indicates if the contest was set from non-finished to finished. |
| 43 | + * This is used to take further action on save of the object. |
| 44 | + * |
| 45 | + * @since 0.1 |
| 46 | + * @var boolean |
| 47 | + */ |
| 48 | + protected $wasSetToFinished = false; |
| 49 | + |
| 50 | + /** |
| 51 | + * Method to get an instance so methods that ought to be static, |
| 52 | + * but can't be due to PHP 5.2 not having LSB, can be called on |
| 53 | + * it. This also allows easy identifying of code that needs to |
| 54 | + * be changed once PHP 5.3 becomes an acceptable requirement. |
| 55 | + * |
| 56 | + * @since 0.1 |
| 57 | + * |
| 58 | + * @return ContestDBObject |
| 59 | + */ |
| 60 | + public static function s() { |
| 61 | + static $instance = false; |
| 62 | + |
| 63 | + if ( $instance === false ) { |
| 64 | + $instance = new self( array() ); |
| 65 | + } |
| 66 | + |
| 67 | + return $instance; |
| 68 | + } |
| 69 | + |
| 70 | + /** |
| 71 | + * Get a new instance of the class from an array. |
| 72 | + * This method ought to be in the basic class and |
| 73 | + * return a new static(), but this requires LSB/PHP>=5.3. |
| 74 | + * |
| 75 | + * @since 0.1 |
| 76 | + * |
| 77 | + * @param array $data |
| 78 | + * @param boolean $loadDefaults |
| 79 | + * |
| 80 | + * @return ContestDBObject |
| 81 | + */ |
| 82 | + public function newFromArray( array $data, $loadDefaults = false ) { |
| 83 | + return new self( $data, $loadDefaults ); |
| 84 | + } |
| 85 | + |
| 86 | + /** |
| 87 | + * @see parent::getFieldTypes |
| 88 | + * |
| 89 | + * @since 0.1 |
| 90 | + * |
| 91 | + * @return string |
| 92 | + */ |
| 93 | + public function getDBTable() { |
| 94 | + return 'contests'; |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * @see parent::getFieldTypes |
| 99 | + * |
| 100 | + * @since 0.1 |
| 101 | + * |
| 102 | + * @return string |
| 103 | + */ |
| 104 | + protected function getFieldPrefix() { |
| 105 | + return 'contest_'; |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * @see parent::getFieldTypes |
| 110 | + * |
| 111 | + * @since 0.1 |
| 112 | + * |
| 113 | + * @return array |
| 114 | + */ |
| 115 | + protected function getFieldTypes() { |
| 116 | + return array( |
| 117 | + 'id' => 'id', |
| 118 | + 'name' => 'str', |
| 119 | + 'status' => 'int', |
| 120 | + 'end' => 'str', // TS_MW |
| 121 | + |
| 122 | + 'rules_page' => 'str', |
| 123 | + 'opportunities' => 'str', |
| 124 | + 'intro' => 'str', |
| 125 | + 'help' => 'str', |
| 126 | + 'signup_email' => 'str', |
| 127 | + 'reminder_email' => 'str', |
| 128 | + |
| 129 | + 'submission_count' => 'int', |
| 130 | + ); |
| 131 | + } |
| 132 | + |
| 133 | + /** |
| 134 | + * @see parent::getDefaults |
| 135 | + * |
| 136 | + * @since 0.1 |
| 137 | + * |
| 138 | + * @return array |
| 139 | + */ |
| 140 | + public function getDefaults() { |
| 141 | + return array( |
| 142 | + 'name' => '', |
| 143 | + 'status' => self::STATUS_DRAFT, |
| 144 | + 'end' => '', |
| 145 | + |
| 146 | + 'rules_page' => 'MediaWiki:Contests/', |
| 147 | + 'opportunities' => 'MediaWiki:Contests/', |
| 148 | + 'intro' => 'MediaWiki:Contests/', |
| 149 | + 'help' => '', |
| 150 | + 'signup_email' => 'MediaWiki:Contests/', |
| 151 | + 'reminder_email' => 'MediaWiki:Contests/', |
| 152 | + |
| 153 | + 'submission_count' => 0, |
| 154 | + ); |
| 155 | + } |
| 156 | + |
| 157 | + /** |
| 158 | + * Gets the message for the provided status. |
| 159 | + * |
| 160 | + * @param Contest::STATUS_ $status |
| 161 | + * |
| 162 | + * @return string |
| 163 | + */ |
| 164 | + public static function getStatusMessage( $status ) { |
| 165 | + static $map = false; |
| 166 | + |
| 167 | + if ( $map === false ) { |
| 168 | + $map = array_flip( self::getStatusMessages() ); |
| 169 | + } |
| 170 | + |
| 171 | + return $map[$status]; |
| 172 | + } |
| 173 | + |
| 174 | + /** |
| 175 | + * Returns a list of status messages and their corresponding constants. |
| 176 | + * |
| 177 | + * @param boolean $onlySettable Whether to return only messages for modifiable status. |
| 178 | + * |
| 179 | + * @since 0.1 |
| 180 | + * |
| 181 | + * @return array |
| 182 | + */ |
| 183 | + public static function getStatusMessages( $onlySettable = false ) { |
| 184 | + static $map = false; |
| 185 | + |
| 186 | + if ( $map === false ) { |
| 187 | + $map = array( |
| 188 | + wfMsg( 'contest-status-draft' ) => self::STATUS_DRAFT, |
| 189 | + wfMsg( 'contest-status-active' ) => self::STATUS_ACTIVE, |
| 190 | + wfMsg( 'contest-status-finished' ) => self::STATUS_FINISHED, |
| 191 | + ); |
| 192 | + } |
| 193 | + |
| 194 | + if ( !$onlySettable ) { |
| 195 | + $map[wfMsg( 'contest-status-expired')] = self::STATUS_EXPIRED; |
| 196 | + } |
| 197 | + |
| 198 | + return $map; |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Load the challenges from the database. |
| 203 | + * Any set challenges will be lost. |
| 204 | + * |
| 205 | + * @since 0.1 |
| 206 | + */ |
| 207 | + public function loadChallenges() { |
| 208 | + $this->challenges = ContestChallenge::s()->select( |
| 209 | + null, |
| 210 | + array( 'contest_id' => $this->getId() ) |
| 211 | + ); |
| 212 | + } |
| 213 | + |
| 214 | + /** |
| 215 | + * Gets the challenges that are part of this contest. |
| 216 | + * |
| 217 | + * @since 0.1 |
| 218 | + * |
| 219 | + * @return array of ContestChallenge |
| 220 | + */ |
| 221 | + public function getChallenges( $forceLoad = false ) { |
| 222 | + if ( is_null( $this->challenges ) || $forceLoad ) { |
| 223 | + $this->loadChallenges(); |
| 224 | + } |
| 225 | + |
| 226 | + return $this->challenges; |
| 227 | + } |
| 228 | + |
| 229 | + /** |
| 230 | + * Load the contestants from the database. |
| 231 | + * Any set contestants will be lost. |
| 232 | + * |
| 233 | + * @since 0.1 |
| 234 | + */ |
| 235 | + public function loadContestants() { |
| 236 | + $this->contestants = ContestContestant::s()->select( |
| 237 | + null, |
| 238 | + array( 'contest_id' => $this->getId() ) |
| 239 | + ); |
| 240 | + } |
| 241 | + |
| 242 | + /** |
| 243 | + * Gets the contestants for this contest. |
| 244 | + * |
| 245 | + * @since 0.1 |
| 246 | + * |
| 247 | + * @return array of ContestContestant |
| 248 | + */ |
| 249 | + public function getContestants( $forceLoad = false ) { |
| 250 | + if ( is_null( $this->contestants ) || $forceLoad ) { |
| 251 | + $this->loadContestants(); |
| 252 | + } |
| 253 | + |
| 254 | + return $this->contestants; |
| 255 | + } |
| 256 | + |
| 257 | + /** |
| 258 | + * Set the contestants for this contest. |
| 259 | + * |
| 260 | + * @since 0.1 |
| 261 | + * |
| 262 | + * @param array $contestants |
| 263 | + */ |
| 264 | + public function setContestants( array /* of ContestContestant */ $contestants ) { |
| 265 | + $this->contestants = $contestants; |
| 266 | + } |
| 267 | + |
| 268 | + /** |
| 269 | + * Set the challenges for this contest. |
| 270 | + * |
| 271 | + * @since 0.1 |
| 272 | + * |
| 273 | + * @param array $challenges |
| 274 | + */ |
| 275 | + public function setChallenges( array /* of ContestChallenge */ $challenges ) { |
| 276 | + $this->challenges = $challenges; |
| 277 | + } |
| 278 | + |
| 279 | + /** |
| 280 | + * (non-PHPdoc) |
| 281 | + * @see ContestDBObject::writeToDB() |
| 282 | + * @return bool |
| 283 | + */ |
| 284 | + public function writeToDB() { |
| 285 | + $success = parent::writeToDB(); |
| 286 | + |
| 287 | + if ( $success && $this->wasSetToFinished ) { |
| 288 | + $this->doFinishActions(); |
| 289 | + $this->wasSetToFinished = false; |
| 290 | + } |
| 291 | + |
| 292 | + return $success; |
| 293 | + } |
| 294 | + |
| 295 | + /** |
| 296 | + * Write the contest and all set challenges and participants to the database. |
| 297 | + * |
| 298 | + * @since 0.1 |
| 299 | + * |
| 300 | + * @return boolean Success indicator |
| 301 | + */ |
| 302 | + public function writeAllToDB() { |
| 303 | + $success = self::writeToDB(); |
| 304 | + |
| 305 | + if ( $success ) { |
| 306 | + $success = $this->writeChallengesToDB(); |
| 307 | + } |
| 308 | + |
| 309 | + if ( $success ) { |
| 310 | + $success = $this->writeContestantsToDB(); |
| 311 | + } |
| 312 | + |
| 313 | + return $success; |
| 314 | + } |
| 315 | + |
| 316 | + /** |
| 317 | + * Write the challenges to the database. |
| 318 | + * |
| 319 | + * @since 0.1 |
| 320 | + * |
| 321 | + * @return boolean Success indicator |
| 322 | + */ |
| 323 | + public function writeChallengesToDB() { |
| 324 | + if ( is_null( $this->challenges ) || count( $this->challenges ) == 0 ) { |
| 325 | + return true; |
| 326 | + } |
| 327 | + |
| 328 | + $dbw = wfGetDB( DB_MASTER ); |
| 329 | + $success = true; |
| 330 | + |
| 331 | + $dbw->begin(); |
| 332 | + |
| 333 | + foreach ( $this->challenges as /* ContestChallenge */ $challenge ) { |
| 334 | + $challenge->setField( 'contest_id', $this->getId() ); |
| 335 | + $success &= $challenge->writeToDB(); |
| 336 | + } |
| 337 | + |
| 338 | + $dbw->commit(); |
| 339 | + |
| 340 | + return $success; |
| 341 | + } |
| 342 | + |
| 343 | + /** |
| 344 | + * Write the contestants to the database. |
| 345 | + * |
| 346 | + * @since 0.1 |
| 347 | + * |
| 348 | + * @return boolean Success indicator |
| 349 | + */ |
| 350 | + public function writeContestantsToDB() { |
| 351 | + if ( is_null( $this->contestants ) || count( $this->contestants ) == 0 ) { |
| 352 | + return true; |
| 353 | + } |
| 354 | + |
| 355 | + $dbw = wfGetDB( DB_MASTER ); |
| 356 | + $success = true; |
| 357 | + $nr = 0; |
| 358 | + |
| 359 | + $dbw->begin(); |
| 360 | + |
| 361 | + foreach ( $this->contestants as /* ContestContestant */ $contestant ) { |
| 362 | + $contestant->setField( 'contest_id', $this->getId() ); |
| 363 | + $success &= $contestant->writeToDB(); |
| 364 | + |
| 365 | + if ( ++$nr % 500 == 0 ) { |
| 366 | + $dbw->commit(); |
| 367 | + $dbw->begin(); |
| 368 | + } |
| 369 | + } |
| 370 | + |
| 371 | + $dbw->commit(); |
| 372 | + |
| 373 | + return $success; |
| 374 | + } |
| 375 | + |
| 376 | + /** |
| 377 | + * Add an amount (can be negative) to the total submissions for this contest. |
| 378 | + * |
| 379 | + * @since 0.1 |
| 380 | + * |
| 381 | + * @param integer $amount |
| 382 | + * |
| 383 | + * @return boolean Success indicator |
| 384 | + */ |
| 385 | + public function addToSubmissionCount( $amount ) { |
| 386 | + return parent::addToField( 'submission_count', $amount ); |
| 387 | + } |
| 388 | + |
| 389 | + /** |
| 390 | + * (non-PHPdoc) |
| 391 | + * @see ContestDBObject::setField() |
| 392 | + */ |
| 393 | + public function setField( $name, $value ) { |
| 394 | + if ( $name == 'status' && $value == self::STATUS_FINISHED |
| 395 | + && $this->hasField( $name ) && $this->getField( $name ) != self::STATUS_FINISHED ) { |
| 396 | + $this->wasSetToFinished = true; |
| 397 | + } |
| 398 | + |
| 399 | + parent::setField( $name, $value ); |
| 400 | + } |
| 401 | + |
| 402 | + /** |
| 403 | + * Remove the contest and all it's linked data from the database. |
| 404 | + * |
| 405 | + * @since 0.1 |
| 406 | + * |
| 407 | + * @return boolean Success indicator |
| 408 | + */ |
| 409 | + public function removeAllFromDB() { |
| 410 | + $condition = array( 'contest_id' => $this->getId() ); |
| 411 | + |
| 412 | + $success = ContestChallenge::s()->delete( $condition ); |
| 413 | + |
| 414 | + if ( $success ) { |
| 415 | + $contestantIds = array(); |
| 416 | + |
| 417 | + foreach ( ContestContestant::s()->select( 'id', $condition ) as /* ContestContestant */ $contestant ) { |
| 418 | + $contestantIds[] = $contestant->getId(); |
| 419 | + } |
| 420 | + |
| 421 | + if ( count( $contestantIds ) > 0 ) { |
| 422 | + $success = ContestComment::s()->delete( array( 'contestant_id' => $contestantIds ) ) && $success; |
| 423 | + $success = ContestVote::s()->delete( array( 'contestant_id' => $contestantIds ) ) && $success; |
| 424 | + } |
| 425 | + |
| 426 | + $success = ContestContestant::s()->delete( $condition ) && $success; |
| 427 | + } |
| 428 | + |
| 429 | + if ( $success ) { |
| 430 | + $success = parent::removeFromDB(); |
| 431 | + } |
| 432 | + |
| 433 | + return $success; |
| 434 | + } |
| 435 | + |
| 436 | + /** |
| 437 | + * Do all actions that need to be done on contest finish. |
| 438 | + * |
| 439 | + * @since 0.1 |
| 440 | + */ |
| 441 | + public function doFinishActions() { |
| 442 | + // TODO |
| 443 | + } |
| 444 | + |
| 445 | + /** |
| 446 | + * Gets the amount of time left, in seconds. |
| 447 | + * |
| 448 | + * @since 0.1 |
| 449 | + * |
| 450 | + * @return integer |
| 451 | + */ |
| 452 | + public function getTimeLeft() { |
| 453 | + return wfTimestamp( TS_UNIX, $this->getField( 'end' ) ) - time(); |
| 454 | + } |
| 455 | + |
| 456 | + /** |
| 457 | + * Gets the amount of days left, rounded up to the nearest integer. |
| 458 | + * |
| 459 | + * @since 0.1 |
| 460 | + * |
| 461 | + * @return integer |
| 462 | + */ |
| 463 | + public function getDaysLeft() { |
| 464 | + return (int)ceil( $this->getTimeLeft() / ( 60 * 60 * 24 ) ); |
| 465 | + } |
| 466 | + |
| 467 | + /** |
| 468 | + * Gets the contest status, which is either expired, or whatever the |
| 469 | + * contest administrator has manually set it to. Only active contests will |
| 470 | + * be evaluated for expiry. |
| 471 | + * |
| 472 | + * @return integer status constant |
| 473 | + * |
| 474 | + **/ |
| 475 | + public function getStatus() { |
| 476 | + $dbStatus = $this->getField( 'status' ); |
| 477 | + |
| 478 | + if ( $dbStatus === self::STATUS_ACTIVE && $this->getTimeLeft() <= 0 ) { |
| 479 | + return self::STATUS_EXPIRED; |
| 480 | + } else { |
| 481 | + return $dbStatus; |
| 482 | + } |
| 483 | + } |
| 484 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/includes/Contest.class.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 485 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/COPYING |
— | — | @@ -0,0 +1,682 @@ |
| 2 | +The license text below "----" applies to all files within this distribution, other |
| 3 | +than those that are in a directory which contains files named "LICENSE" or |
| 4 | +"COPYING", or a subdirectory thereof. For those files, the license text contained in |
| 5 | +said file overrides any license information contained in directories of smaller depth. |
| 6 | +Alternative licenses are typically used for software that is provided by external |
| 7 | +parties, and merely packaged with this software for convenience. |
| 8 | +---- |
| 9 | + |
| 10 | + GNU GENERAL PUBLIC LICENSE |
| 11 | + Version 3, 29 June 2007 |
| 12 | + |
| 13 | + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
| 14 | + Everyone is permitted to copy and distribute verbatim copies |
| 15 | + of this license document, but changing it is not allowed. |
| 16 | + |
| 17 | + Preamble |
| 18 | + |
| 19 | + The GNU General Public License is a free, copyleft license for |
| 20 | +software and other kinds of works. |
| 21 | + |
| 22 | + The licenses for most software and other practical works are designed |
| 23 | +to take away your freedom to share and change the works. By contrast, |
| 24 | +the GNU General Public License is intended to guarantee your freedom to |
| 25 | +share and change all versions of a program--to make sure it remains free |
| 26 | +software for all its users. We, the Free Software Foundation, use the |
| 27 | +GNU General Public License for most of our software; it applies also to |
| 28 | +any other work released this way by its authors. You can apply it to |
| 29 | +your programs, too. |
| 30 | + |
| 31 | + When we speak of free software, we are referring to freedom, not |
| 32 | +price. Our General Public Licenses are designed to make sure that you |
| 33 | +have the freedom to distribute copies of free software (and charge for |
| 34 | +them if you wish), that you receive source code or can get it if you |
| 35 | +want it, that you can change the software or use pieces of it in new |
| 36 | +free programs, and that you know you can do these things. |
| 37 | + |
| 38 | + To protect your rights, we need to prevent others from denying you |
| 39 | +these rights or asking you to surrender the rights. Therefore, you have |
| 40 | +certain responsibilities if you distribute copies of the software, or if |
| 41 | +you modify it: responsibilities to respect the freedom of others. |
| 42 | + |
| 43 | + For example, if you distribute copies of such a program, whether |
| 44 | +gratis or for a fee, you must pass on to the recipients the same |
| 45 | +freedoms that you received. You must make sure that they, too, receive |
| 46 | +or can get the source code. And you must show them these terms so they |
| 47 | +know their rights. |
| 48 | + |
| 49 | + Developers that use the GNU GPL protect your rights with two steps: |
| 50 | +(1) assert copyright on the software, and (2) offer you this License |
| 51 | +giving you legal permission to copy, distribute and/or modify it. |
| 52 | + |
| 53 | + For the developers' and authors' protection, the GPL clearly explains |
| 54 | +that there is no warranty for this free software. For both users' and |
| 55 | +authors' sake, the GPL requires that modified versions be marked as |
| 56 | +changed, so that their problems will not be attributed erroneously to |
| 57 | +authors of previous versions. |
| 58 | + |
| 59 | + Some devices are designed to deny users access to install or run |
| 60 | +modified versions of the software inside them, although the manufacturer |
| 61 | +can do so. This is fundamentally incompatible with the aim of |
| 62 | +protecting users' freedom to change the software. The systematic |
| 63 | +pattern of such abuse occurs in the area of products for individuals to |
| 64 | +use, which is precisely where it is most unacceptable. Therefore, we |
| 65 | +have designed this version of the GPL to prohibit the practice for those |
| 66 | +products. If such problems arise substantially in other domains, we |
| 67 | +stand ready to extend this provision to those domains in future versions |
| 68 | +of the GPL, as needed to protect the freedom of users. |
| 69 | + |
| 70 | + Finally, every program is threatened constantly by software patents. |
| 71 | +States should not allow patents to restrict development and use of |
| 72 | +software on general-purpose computers, but in those that do, we wish to |
| 73 | +avoid the special danger that patents applied to a free program could |
| 74 | +make it effectively proprietary. To prevent this, the GPL assures that |
| 75 | +patents cannot be used to render the program non-free. |
| 76 | + |
| 77 | + The precise terms and conditions for copying, distribution and |
| 78 | +modification follow. |
| 79 | + |
| 80 | + TERMS AND CONDITIONS |
| 81 | + |
| 82 | + 0. Definitions. |
| 83 | + |
| 84 | + "This License" refers to version 3 of the GNU General Public License. |
| 85 | + |
| 86 | + "Copyright" also means copyright-like laws that apply to other kinds of |
| 87 | +works, such as semiconductor masks. |
| 88 | + |
| 89 | + "The Program" refers to any copyrightable work licensed under this |
| 90 | +License. Each licensee is addressed as "you". "Licensees" and |
| 91 | +"recipients" may be individuals or organizations. |
| 92 | + |
| 93 | + To "modify" a work means to copy from or adapt all or part of the work |
| 94 | +in a fashion requiring copyright permission, other than the making of an |
| 95 | +exact copy. The resulting work is called a "modified version" of the |
| 96 | +earlier work or a work "based on" the earlier work. |
| 97 | + |
| 98 | + A "covered work" means either the unmodified Program or a work based |
| 99 | +on the Program. |
| 100 | + |
| 101 | + To "propagate" a work means to do anything with it that, without |
| 102 | +permission, would make you directly or secondarily liable for |
| 103 | +infringement under applicable copyright law, except executing it on a |
| 104 | +computer or modifying a private copy. Propagation includes copying, |
| 105 | +distribution (with or without modification), making available to the |
| 106 | +public, and in some countries other activities as well. |
| 107 | + |
| 108 | + To "convey" a work means any kind of propagation that enables other |
| 109 | +parties to make or receive copies. Mere interaction with a user through |
| 110 | +a computer network, with no transfer of a copy, is not conveying. |
| 111 | + |
| 112 | + An interactive user interface displays "Appropriate Legal Notices" |
| 113 | +to the extent that it includes a convenient and prominently visible |
| 114 | +feature that (1) displays an appropriate copyright notice, and (2) |
| 115 | +tells the user that there is no warranty for the work (except to the |
| 116 | +extent that warranties are provided), that licensees may convey the |
| 117 | +work under this License, and how to view a copy of this License. If |
| 118 | +the interface presents a list of user commands or options, such as a |
| 119 | +menu, a prominent item in the list meets this criterion. |
| 120 | + |
| 121 | + 1. Source Code. |
| 122 | + |
| 123 | + The "source code" for a work means the preferred form of the work |
| 124 | +for making modifications to it. "Object code" means any non-source |
| 125 | +form of a work. |
| 126 | + |
| 127 | + A "Standard Interface" means an interface that either is an official |
| 128 | +standard defined by a recognized standards body, or, in the case of |
| 129 | +interfaces specified for a particular programming language, one that |
| 130 | +is widely used among developers working in that language. |
| 131 | + |
| 132 | + The "System Libraries" of an executable work include anything, other |
| 133 | +than the work as a whole, that (a) is included in the normal form of |
| 134 | +packaging a Major Component, but which is not part of that Major |
| 135 | +Component, and (b) serves only to enable use of the work with that |
| 136 | +Major Component, or to implement a Standard Interface for which an |
| 137 | +implementation is available to the public in source code form. A |
| 138 | +"Major Component", in this context, means a major essential component |
| 139 | +(kernel, window system, and so on) of the specific operating system |
| 140 | +(if any) on which the executable work runs, or a compiler used to |
| 141 | +produce the work, or an object code interpreter used to run it. |
| 142 | + |
| 143 | + The "Corresponding Source" for a work in object code form means all |
| 144 | +the source code needed to generate, install, and (for an executable |
| 145 | +work) run the object code and to modify the work, including scripts to |
| 146 | +control those activities. However, it does not include the work's |
| 147 | +System Libraries, or general-purpose tools or generally available free |
| 148 | +programs which are used unmodified in performing those activities but |
| 149 | +which are not part of the work. For example, Corresponding Source |
| 150 | +includes interface definition files associated with source files for |
| 151 | +the work, and the source code for shared libraries and dynamically |
| 152 | +linked subprograms that the work is specifically designed to require, |
| 153 | +such as by intimate data communication or control flow between those |
| 154 | +subprograms and other parts of the work. |
| 155 | + |
| 156 | + The Corresponding Source need not include anything that users |
| 157 | +can regenerate automatically from other parts of the Corresponding |
| 158 | +Source. |
| 159 | + |
| 160 | + The Corresponding Source for a work in source code form is that |
| 161 | +same work. |
| 162 | + |
| 163 | + 2. Basic Permissions. |
| 164 | + |
| 165 | + All rights granted under this License are granted for the term of |
| 166 | +copyright on the Program, and are irrevocable provided the stated |
| 167 | +conditions are met. This License explicitly affirms your unlimited |
| 168 | +permission to run the unmodified Program. The output from running a |
| 169 | +covered work is covered by this License only if the output, given its |
| 170 | +content, constitutes a covered work. This License acknowledges your |
| 171 | +rights of fair use or other equivalent, as provided by copyright law. |
| 172 | + |
| 173 | + You may make, run and propagate covered works that you do not |
| 174 | +convey, without conditions so long as your license otherwise remains |
| 175 | +in force. You may convey covered works to others for the sole purpose |
| 176 | +of having them make modifications exclusively for you, or provide you |
| 177 | +with facilities for running those works, provided that you comply with |
| 178 | +the terms of this License in conveying all material for which you do |
| 179 | +not control copyright. Those thus making or running the covered works |
| 180 | +for you must do so exclusively on your behalf, under your direction |
| 181 | +and control, on terms that prohibit them from making any copies of |
| 182 | +your copyrighted material outside their relationship with you. |
| 183 | + |
| 184 | + Conveying under any other circumstances is permitted solely under |
| 185 | +the conditions stated below. Sublicensing is not allowed; section 10 |
| 186 | +makes it unnecessary. |
| 187 | + |
| 188 | + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
| 189 | + |
| 190 | + No covered work shall be deemed part of an effective technological |
| 191 | +measure under any applicable law fulfilling obligations under article |
| 192 | +11 of the WIPO copyright treaty adopted on 20 December 1996, or |
| 193 | +similar laws prohibiting or restricting circumvention of such |
| 194 | +measures. |
| 195 | + |
| 196 | + When you convey a covered work, you waive any legal power to forbid |
| 197 | +circumvention of technological measures to the extent such circumvention |
| 198 | +is effected by exercising rights under this License with respect to |
| 199 | +the covered work, and you disclaim any intention to limit operation or |
| 200 | +modification of the work as a means of enforcing, against the work's |
| 201 | +users, your or third parties' legal rights to forbid circumvention of |
| 202 | +technological measures. |
| 203 | + |
| 204 | + 4. Conveying Verbatim Copies. |
| 205 | + |
| 206 | + You may convey verbatim copies of the Program's source code as you |
| 207 | +receive it, in any medium, provided that you conspicuously and |
| 208 | +appropriately publish on each copy an appropriate copyright notice; |
| 209 | +keep intact all notices stating that this License and any |
| 210 | +non-permissive terms added in accord with section 7 apply to the code; |
| 211 | +keep intact all notices of the absence of any warranty; and give all |
| 212 | +recipients a copy of this License along with the Program. |
| 213 | + |
| 214 | + You may charge any price or no price for each copy that you convey, |
| 215 | +and you may offer support or warranty protection for a fee. |
| 216 | + |
| 217 | + 5. Conveying Modified Source Versions. |
| 218 | + |
| 219 | + You may convey a work based on the Program, or the modifications to |
| 220 | +produce it from the Program, in the form of source code under the |
| 221 | +terms of section 4, provided that you also meet all of these conditions: |
| 222 | + |
| 223 | + a) The work must carry prominent notices stating that you modified |
| 224 | + it, and giving a relevant date. |
| 225 | + |
| 226 | + b) The work must carry prominent notices stating that it is |
| 227 | + released under this License and any conditions added under section |
| 228 | + 7. This requirement modifies the requirement in section 4 to |
| 229 | + "keep intact all notices". |
| 230 | + |
| 231 | + c) You must license the entire work, as a whole, under this |
| 232 | + License to anyone who comes into possession of a copy. This |
| 233 | + License will therefore apply, along with any applicable section 7 |
| 234 | + additional terms, to the whole of the work, and all its parts, |
| 235 | + regardless of how they are packaged. This License gives no |
| 236 | + permission to license the work in any other way, but it does not |
| 237 | + invalidate such permission if you have separately received it. |
| 238 | + |
| 239 | + d) If the work has interactive user interfaces, each must display |
| 240 | + Appropriate Legal Notices; however, if the Program has interactive |
| 241 | + interfaces that do not display Appropriate Legal Notices, your |
| 242 | + work need not make them do so. |
| 243 | + |
| 244 | + A compilation of a covered work with other separate and independent |
| 245 | +works, which are not by their nature extensions of the covered work, |
| 246 | +and which are not combined with it such as to form a larger program, |
| 247 | +in or on a volume of a storage or distribution medium, is called an |
| 248 | +"aggregate" if the compilation and its resulting copyright are not |
| 249 | +used to limit the access or legal rights of the compilation's users |
| 250 | +beyond what the individual works permit. Inclusion of a covered work |
| 251 | +in an aggregate does not cause this License to apply to the other |
| 252 | +parts of the aggregate. |
| 253 | + |
| 254 | + 6. Conveying Non-Source Forms. |
| 255 | + |
| 256 | + You may convey a covered work in object code form under the terms |
| 257 | +of sections 4 and 5, provided that you also convey the |
| 258 | +machine-readable Corresponding Source under the terms of this License, |
| 259 | +in one of these ways: |
| 260 | + |
| 261 | + a) Convey the object code in, or embodied in, a physical product |
| 262 | + (including a physical distribution medium), accompanied by the |
| 263 | + Corresponding Source fixed on a durable physical medium |
| 264 | + customarily used for software interchange. |
| 265 | + |
| 266 | + b) Convey the object code in, or embodied in, a physical product |
| 267 | + (including a physical distribution medium), accompanied by a |
| 268 | + written offer, valid for at least three years and valid for as |
| 269 | + long as you offer spare parts or customer support for that product |
| 270 | + model, to give anyone who possesses the object code either (1) a |
| 271 | + copy of the Corresponding Source for all the software in the |
| 272 | + product that is covered by this License, on a durable physical |
| 273 | + medium customarily used for software interchange, for a price no |
| 274 | + more than your reasonable cost of physically performing this |
| 275 | + conveying of source, or (2) access to copy the |
| 276 | + Corresponding Source from a network server at no charge. |
| 277 | + |
| 278 | + c) Convey individual copies of the object code with a copy of the |
| 279 | + written offer to provide the Corresponding Source. This |
| 280 | + alternative is allowed only occasionally and noncommercially, and |
| 281 | + only if you received the object code with such an offer, in accord |
| 282 | + with subsection 6b. |
| 283 | + |
| 284 | + d) Convey the object code by offering access from a designated |
| 285 | + place (gratis or for a charge), and offer equivalent access to the |
| 286 | + Corresponding Source in the same way through the same place at no |
| 287 | + further charge. You need not require recipients to copy the |
| 288 | + Corresponding Source along with the object code. If the place to |
| 289 | + copy the object code is a network server, the Corresponding Source |
| 290 | + may be on a different server (operated by you or a third party) |
| 291 | + that supports equivalent copying facilities, provided you maintain |
| 292 | + clear directions next to the object code saying where to find the |
| 293 | + Corresponding Source. Regardless of what server hosts the |
| 294 | + Corresponding Source, you remain obligated to ensure that it is |
| 295 | + available for as long as needed to satisfy these requirements. |
| 296 | + |
| 297 | + e) Convey the object code using peer-to-peer transmission, provided |
| 298 | + you inform other peers where the object code and Corresponding |
| 299 | + Source of the work are being offered to the general public at no |
| 300 | + charge under subsection 6d. |
| 301 | + |
| 302 | + A separable portion of the object code, whose source code is excluded |
| 303 | +from the Corresponding Source as a System Library, need not be |
| 304 | +included in conveying the object code work. |
| 305 | + |
| 306 | + A "User Product" is either (1) a "consumer product", which means any |
| 307 | +tangible personal property which is normally used for personal, family, |
| 308 | +or household purposes, or (2) anything designed or sold for incorporation |
| 309 | +into a dwelling. In determining whether a product is a consumer product, |
| 310 | +doubtful cases shall be resolved in favor of coverage. For a particular |
| 311 | +product received by a particular user, "normally used" refers to a |
| 312 | +typical or common use of that class of product, regardless of the status |
| 313 | +of the particular user or of the way in which the particular user |
| 314 | +actually uses, or expects or is expected to use, the product. A product |
| 315 | +is a consumer product regardless of whether the product has substantial |
| 316 | +commercial, industrial or non-consumer uses, unless such uses represent |
| 317 | +the only significant mode of use of the product. |
| 318 | + |
| 319 | + "Installation Information" for a User Product means any methods, |
| 320 | +procedures, authorization keys, or other information required to install |
| 321 | +and execute modified versions of a covered work in that User Product from |
| 322 | +a modified version of its Corresponding Source. The information must |
| 323 | +suffice to ensure that the continued functioning of the modified object |
| 324 | +code is in no case prevented or interfered with solely because |
| 325 | +modification has been made. |
| 326 | + |
| 327 | + If you convey an object code work under this section in, or with, or |
| 328 | +specifically for use in, a User Product, and the conveying occurs as |
| 329 | +part of a transaction in which the right of possession and use of the |
| 330 | +User Product is transferred to the recipient in perpetuity or for a |
| 331 | +fixed term (regardless of how the transaction is characterized), the |
| 332 | +Corresponding Source conveyed under this section must be accompanied |
| 333 | +by the Installation Information. But this requirement does not apply |
| 334 | +if neither you nor any third party retains the ability to install |
| 335 | +modified object code on the User Product (for example, the work has |
| 336 | +been installed in ROM). |
| 337 | + |
| 338 | + The requirement to provide Installation Information does not include a |
| 339 | +requirement to continue to provide support service, warranty, or updates |
| 340 | +for a work that has been modified or installed by the recipient, or for |
| 341 | +the User Product in which it has been modified or installed. Access to a |
| 342 | +network may be denied when the modification itself materially and |
| 343 | +adversely affects the operation of the network or violates the rules and |
| 344 | +protocols for communication across the network. |
| 345 | + |
| 346 | + Corresponding Source conveyed, and Installation Information provided, |
| 347 | +in accord with this section must be in a format that is publicly |
| 348 | +documented (and with an implementation available to the public in |
| 349 | +source code form), and must require no special password or key for |
| 350 | +unpacking, reading or copying. |
| 351 | + |
| 352 | + 7. Additional Terms. |
| 353 | + |
| 354 | + "Additional permissions" are terms that supplement the terms of this |
| 355 | +License by making exceptions from one or more of its conditions. |
| 356 | +Additional permissions that are applicable to the entire Program shall |
| 357 | +be treated as though they were included in this License, to the extent |
| 358 | +that they are valid under applicable law. If additional permissions |
| 359 | +apply only to part of the Program, that part may be used separately |
| 360 | +under those permissions, but the entire Program remains governed by |
| 361 | +this License without regard to the additional permissions. |
| 362 | + |
| 363 | + When you convey a copy of a covered work, you may at your option |
| 364 | +remove any additional permissions from that copy, or from any part of |
| 365 | +it. (Additional permissions may be written to require their own |
| 366 | +removal in certain cases when you modify the work.) You may place |
| 367 | +additional permissions on material, added by you to a covered work, |
| 368 | +for which you have or can give appropriate copyright permission. |
| 369 | + |
| 370 | + Notwithstanding any other provision of this License, for material you |
| 371 | +add to a covered work, you may (if authorized by the copyright holders of |
| 372 | +that material) supplement the terms of this License with terms: |
| 373 | + |
| 374 | + a) Disclaiming warranty or limiting liability differently from the |
| 375 | + terms of sections 15 and 16 of this License; or |
| 376 | + |
| 377 | + b) Requiring preservation of specified reasonable legal notices or |
| 378 | + author attributions in that material or in the Appropriate Legal |
| 379 | + Notices displayed by works containing it; or |
| 380 | + |
| 381 | + c) Prohibiting misrepresentation of the origin of that material, or |
| 382 | + requiring that modified versions of such material be marked in |
| 383 | + reasonable ways as different from the original version; or |
| 384 | + |
| 385 | + d) Limiting the use for publicity purposes of names of licensors or |
| 386 | + authors of the material; or |
| 387 | + |
| 388 | + e) Declining to grant rights under trademark law for use of some |
| 389 | + trade names, trademarks, or service marks; or |
| 390 | + |
| 391 | + f) Requiring indemnification of licensors and authors of that |
| 392 | + material by anyone who conveys the material (or modified versions of |
| 393 | + it) with contractual assumptions of liability to the recipient, for |
| 394 | + any liability that these contractual assumptions directly impose on |
| 395 | + those licensors and authors. |
| 396 | + |
| 397 | + All other non-permissive additional terms are considered "further |
| 398 | +restrictions" within the meaning of section 10. If the Program as you |
| 399 | +received it, or any part of it, contains a notice stating that it is |
| 400 | +governed by this License along with a term that is a further |
| 401 | +restriction, you may remove that term. If a license document contains |
| 402 | +a further restriction but permits relicensing or conveying under this |
| 403 | +License, you may add to a covered work material governed by the terms |
| 404 | +of that license document, provided that the further restriction does |
| 405 | +not survive such relicensing or conveying. |
| 406 | + |
| 407 | + If you add terms to a covered work in accord with this section, you |
| 408 | +must place, in the relevant source files, a statement of the |
| 409 | +additional terms that apply to those files, or a notice indicating |
| 410 | +where to find the applicable terms. |
| 411 | + |
| 412 | + Additional terms, permissive or non-permissive, may be stated in the |
| 413 | +form of a separately written license, or stated as exceptions; |
| 414 | +the above requirements apply either way. |
| 415 | + |
| 416 | + 8. Termination. |
| 417 | + |
| 418 | + You may not propagate or modify a covered work except as expressly |
| 419 | +provided under this License. Any attempt otherwise to propagate or |
| 420 | +modify it is void, and will automatically terminate your rights under |
| 421 | +this License (including any patent licenses granted under the third |
| 422 | +paragraph of section 11). |
| 423 | + |
| 424 | + However, if you cease all violation of this License, then your |
| 425 | +license from a particular copyright holder is reinstated (a) |
| 426 | +provisionally, unless and until the copyright holder explicitly and |
| 427 | +finally terminates your license, and (b) permanently, if the copyright |
| 428 | +holder fails to notify you of the violation by some reasonable means |
| 429 | +prior to 60 days after the cessation. |
| 430 | + |
| 431 | + Moreover, your license from a particular copyright holder is |
| 432 | +reinstated permanently if the copyright holder notifies you of the |
| 433 | +violation by some reasonable means, this is the first time you have |
| 434 | +received notice of violation of this License (for any work) from that |
| 435 | +copyright holder, and you cure the violation prior to 30 days after |
| 436 | +your receipt of the notice. |
| 437 | + |
| 438 | + Termination of your rights under this section does not terminate the |
| 439 | +licenses of parties who have received copies or rights from you under |
| 440 | +this License. If your rights have been terminated and not permanently |
| 441 | +reinstated, you do not qualify to receive new licenses for the same |
| 442 | +material under section 10. |
| 443 | + |
| 444 | + 9. Acceptance Not Required for Having Copies. |
| 445 | + |
| 446 | + You are not required to accept this License in order to receive or |
| 447 | +run a copy of the Program. Ancillary propagation of a covered work |
| 448 | +occurring solely as a consequence of using peer-to-peer transmission |
| 449 | +to receive a copy likewise does not require acceptance. However, |
| 450 | +nothing other than this License grants you permission to propagate or |
| 451 | +modify any covered work. These actions infringe copyright if you do |
| 452 | +not accept this License. Therefore, by modifying or propagating a |
| 453 | +covered work, you indicate your acceptance of this License to do so. |
| 454 | + |
| 455 | + 10. Automatic Licensing of Downstream Recipients. |
| 456 | + |
| 457 | + Each time you convey a covered work, the recipient automatically |
| 458 | +receives a license from the original licensors, to run, modify and |
| 459 | +propagate that work, subject to this License. You are not responsible |
| 460 | +for enforcing compliance by third parties with this License. |
| 461 | + |
| 462 | + An "entity transaction" is a transaction transferring control of an |
| 463 | +organization, or substantially all assets of one, or subdividing an |
| 464 | +organization, or merging organizations. If propagation of a covered |
| 465 | +work results from an entity transaction, each party to that |
| 466 | +transaction who receives a copy of the work also receives whatever |
| 467 | +licenses to the work the party's predecessor in interest had or could |
| 468 | +give under the previous paragraph, plus a right to possession of the |
| 469 | +Corresponding Source of the work from the predecessor in interest, if |
| 470 | +the predecessor has it or can get it with reasonable efforts. |
| 471 | + |
| 472 | + You may not impose any further restrictions on the exercise of the |
| 473 | +rights granted or affirmed under this License. For example, you may |
| 474 | +not impose a license fee, royalty, or other charge for exercise of |
| 475 | +rights granted under this License, and you may not initiate litigation |
| 476 | +(including a cross-claim or counterclaim in a lawsuit) alleging that |
| 477 | +any patent claim is infringed by making, using, selling, offering for |
| 478 | +sale, or importing the Program or any portion of it. |
| 479 | + |
| 480 | + 11. Patents. |
| 481 | + |
| 482 | + A "contributor" is a copyright holder who authorizes use under this |
| 483 | +License of the Program or a work on which the Program is based. The |
| 484 | +work thus licensed is called the contributor's "contributor version". |
| 485 | + |
| 486 | + A contributor's "essential patent claims" are all patent claims |
| 487 | +owned or controlled by the contributor, whether already acquired or |
| 488 | +hereafter acquired, that would be infringed by some manner, permitted |
| 489 | +by this License, of making, using, or selling its contributor version, |
| 490 | +but do not include claims that would be infringed only as a |
| 491 | +consequence of further modification of the contributor version. For |
| 492 | +purposes of this definition, "control" includes the right to grant |
| 493 | +patent sublicenses in a manner consistent with the requirements of |
| 494 | +this License. |
| 495 | + |
| 496 | + Each contributor grants you a non-exclusive, worldwide, royalty-free |
| 497 | +patent license under the contributor's essential patent claims, to |
| 498 | +make, use, sell, offer for sale, import and otherwise run, modify and |
| 499 | +propagate the contents of its contributor version. |
| 500 | + |
| 501 | + In the following three paragraphs, a "patent license" is any express |
| 502 | +agreement or commitment, however denominated, not to enforce a patent |
| 503 | +(such as an express permission to practice a patent or covenant not to |
| 504 | +sue for patent infringement). To "grant" such a patent license to a |
| 505 | +party means to make such an agreement or commitment not to enforce a |
| 506 | +patent against the party. |
| 507 | + |
| 508 | + If you convey a covered work, knowingly relying on a patent license, |
| 509 | +and the Corresponding Source of the work is not available for anyone |
| 510 | +to copy, free of charge and under the terms of this License, through a |
| 511 | +publicly available network server or other readily accessible means, |
| 512 | +then you must either (1) cause the Corresponding Source to be so |
| 513 | +available, or (2) arrange to deprive yourself of the benefit of the |
| 514 | +patent license for this particular work, or (3) arrange, in a manner |
| 515 | +consistent with the requirements of this License, to extend the patent |
| 516 | +license to downstream recipients. "Knowingly relying" means you have |
| 517 | +actual knowledge that, but for the patent license, your conveying the |
| 518 | +covered work in a country, or your recipient's use of the covered work |
| 519 | +in a country, would infringe one or more identifiable patents in that |
| 520 | +country that you have reason to believe are valid. |
| 521 | + |
| 522 | + If, pursuant to or in connection with a single transaction or |
| 523 | +arrangement, you convey, or propagate by procuring conveyance of, a |
| 524 | +covered work, and grant a patent license to some of the parties |
| 525 | +receiving the covered work authorizing them to use, propagate, modify |
| 526 | +or convey a specific copy of the covered work, then the patent license |
| 527 | +you grant is automatically extended to all recipients of the covered |
| 528 | +work and works based on it. |
| 529 | + |
| 530 | + A patent license is "discriminatory" if it does not include within |
| 531 | +the scope of its coverage, prohibits the exercise of, or is |
| 532 | +conditioned on the non-exercise of one or more of the rights that are |
| 533 | +specifically granted under this License. You may not convey a covered |
| 534 | +work if you are a party to an arrangement with a third party that is |
| 535 | +in the business of distributing software, under which you make payment |
| 536 | +to the third party based on the extent of your activity of conveying |
| 537 | +the work, and under which the third party grants, to any of the |
| 538 | +parties who would receive the covered work from you, a discriminatory |
| 539 | +patent license (a) in connection with copies of the covered work |
| 540 | +conveyed by you (or copies made from those copies), or (b) primarily |
| 541 | +for and in connection with specific products or compilations that |
| 542 | +contain the covered work, unless you entered into that arrangement, |
| 543 | +or that patent license was granted, prior to 28 March 2007. |
| 544 | + |
| 545 | + Nothing in this License shall be construed as excluding or limiting |
| 546 | +any implied license or other defenses to infringement that may |
| 547 | +otherwise be available to you under applicable patent law. |
| 548 | + |
| 549 | + 12. No Surrender of Others' Freedom. |
| 550 | + |
| 551 | + If conditions are imposed on you (whether by court order, agreement or |
| 552 | +otherwise) that contradict the conditions of this License, they do not |
| 553 | +excuse you from the conditions of this License. If you cannot convey a |
| 554 | +covered work so as to satisfy simultaneously your obligations under this |
| 555 | +License and any other pertinent obligations, then as a consequence you may |
| 556 | +not convey it at all. For example, if you agree to terms that obligate you |
| 557 | +to collect a royalty for further conveying from those to whom you convey |
| 558 | +the Program, the only way you could satisfy both those terms and this |
| 559 | +License would be to refrain entirely from conveying the Program. |
| 560 | + |
| 561 | + 13. Use with the GNU Affero General Public License. |
| 562 | + |
| 563 | + Notwithstanding any other provision of this License, you have |
| 564 | +permission to link or combine any covered work with a work licensed |
| 565 | +under version 3 of the GNU Affero General Public License into a single |
| 566 | +combined work, and to convey the resulting work. The terms of this |
| 567 | +License will continue to apply to the part which is the covered work, |
| 568 | +but the special requirements of the GNU Affero General Public License, |
| 569 | +section 13, concerning interaction through a network will apply to the |
| 570 | +combination as such. |
| 571 | + |
| 572 | + 14. Revised Versions of this License. |
| 573 | + |
| 574 | + The Free Software Foundation may publish revised and/or new versions of |
| 575 | +the GNU General Public License from time to time. Such new versions will |
| 576 | +be similar in spirit to the present version, but may differ in detail to |
| 577 | +address new problems or concerns. |
| 578 | + |
| 579 | + Each version is given a distinguishing version number. If the |
| 580 | +Program specifies that a certain numbered version of the GNU General |
| 581 | +Public License "or any later version" applies to it, you have the |
| 582 | +option of following the terms and conditions either of that numbered |
| 583 | +version or of any later version published by the Free Software |
| 584 | +Foundation. If the Program does not specify a version number of the |
| 585 | +GNU General Public License, you may choose any version ever published |
| 586 | +by the Free Software Foundation. |
| 587 | + |
| 588 | + If the Program specifies that a proxy can decide which future |
| 589 | +versions of the GNU General Public License can be used, that proxy's |
| 590 | +public statement of acceptance of a version permanently authorizes you |
| 591 | +to choose that version for the Program. |
| 592 | + |
| 593 | + Later license versions may give you additional or different |
| 594 | +permissions. However, no additional obligations are imposed on any |
| 595 | +author or copyright holder as a result of your choosing to follow a |
| 596 | +later version. |
| 597 | + |
| 598 | + 15. Disclaimer of Warranty. |
| 599 | + |
| 600 | + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
| 601 | +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
| 602 | +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
| 603 | +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
| 604 | +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 605 | +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
| 606 | +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
| 607 | +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
| 608 | + |
| 609 | + 16. Limitation of Liability. |
| 610 | + |
| 611 | + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
| 612 | +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
| 613 | +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
| 614 | +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
| 615 | +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
| 616 | +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
| 617 | +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
| 618 | +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
| 619 | +SUCH DAMAGES. |
| 620 | + |
| 621 | + 17. Interpretation of Sections 15 and 16. |
| 622 | + |
| 623 | + If the disclaimer of warranty and limitation of liability provided |
| 624 | +above cannot be given local legal effect according to their terms, |
| 625 | +reviewing courts shall apply local law that most closely approximates |
| 626 | +an absolute waiver of all civil liability in connection with the |
| 627 | +Program, unless a warranty or assumption of liability accompanies a |
| 628 | +copy of the Program in return for a fee. |
| 629 | + |
| 630 | + END OF TERMS AND CONDITIONS |
| 631 | + |
| 632 | + How to Apply These Terms to Your New Programs |
| 633 | + |
| 634 | + If you develop a new program, and you want it to be of the greatest |
| 635 | +possible use to the public, the best way to achieve this is to make it |
| 636 | +free software which everyone can redistribute and change under these terms. |
| 637 | + |
| 638 | + To do so, attach the following notices to the program. It is safest |
| 639 | +to attach them to the start of each source file to most effectively |
| 640 | +state the exclusion of warranty; and each file should have at least |
| 641 | +the "copyright" line and a pointer to where the full notice is found. |
| 642 | + |
| 643 | + <one line to give the program's name and a brief idea of what it does.> |
| 644 | + Copyright (C) <year> <name of author> |
| 645 | + |
| 646 | + This program is free software: you can redistribute it and/or modify |
| 647 | + it under the terms of the GNU General Public License as published by |
| 648 | + the Free Software Foundation, either version 3 of the License, or |
| 649 | + (at your option) any later version. |
| 650 | + |
| 651 | + This program is distributed in the hope that it will be useful, |
| 652 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 653 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 654 | + GNU General Public License for more details. |
| 655 | + |
| 656 | + You should have received a copy of the GNU General Public License |
| 657 | + along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 658 | + |
| 659 | +Also add information on how to contact you by electronic and paper mail. |
| 660 | + |
| 661 | + If the program does terminal interaction, make it output a short |
| 662 | +notice like this when it starts in an interactive mode: |
| 663 | + |
| 664 | + <program> Copyright (C) <year> <name of author> |
| 665 | + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
| 666 | + This is free software, and you are welcome to redistribute it |
| 667 | + under certain conditions; type `show c' for details. |
| 668 | + |
| 669 | +The hypothetical commands `show w' and `show c' should show the appropriate |
| 670 | +parts of the General Public License. Of course, your program's commands |
| 671 | +might be different; for a GUI interface, you would use an "about box". |
| 672 | + |
| 673 | + You should also get your employer (if you work as a programmer) or school, |
| 674 | +if any, to sign a "copyright disclaimer" for the program, if necessary. |
| 675 | +For more information on this, and how to apply and follow the GNU GPL, see |
| 676 | +<http://www.gnu.org/licenses/>. |
| 677 | + |
| 678 | + The GNU General Public License does not permit incorporating your program |
| 679 | +into proprietary programs. If your program is a subroutine library, you |
| 680 | +may consider it more useful to permit linking proprietary applications with |
| 681 | +the library. If this is what you want to do, use the GNU Lesser General |
| 682 | +Public License instead of this License. But first, please read |
| 683 | +<http://www.gnu.org/philosophy/why-not-lgpl.html>. |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/COPYING |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 684 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/api/ApiDeleteContest.php |
— | — | @@ -0,0 +1,98 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * API module to delete contests. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ApiDeleteContest.php |
| 10 | + * @ingroup Contest |
| 11 | + * @ingroup API |
| 12 | + * |
| 13 | + * @licence GNU GPL v3+ |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ApiDeleteContest extends ApiBase { |
| 17 | + |
| 18 | + public function __construct( $main, $action ) { |
| 19 | + parent::__construct( $main, $action ); |
| 20 | + } |
| 21 | + |
| 22 | + public function execute() { |
| 23 | + global $wgUser; |
| 24 | + |
| 25 | + if ( !$wgUser->isAllowed( 'contestadmin' ) || $wgUser->isBlocked() ) { |
| 26 | + $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 27 | + } |
| 28 | + |
| 29 | + $params = $this->extractRequestParams(); |
| 30 | + |
| 31 | + $everythingOk = true; |
| 32 | + |
| 33 | + foreach ( $params['ids'] as $id ) { |
| 34 | + $contest = new Contest( array( 'id' => $id ) ); |
| 35 | + $everythingOk = $contest->removeAllFromDB() && $everythingOk; |
| 36 | + } |
| 37 | + |
| 38 | + $this->getResult()->addValue( |
| 39 | + null, |
| 40 | + 'success', |
| 41 | + $everythingOk |
| 42 | + ); |
| 43 | + } |
| 44 | + |
| 45 | + public function needsToken() { |
| 46 | + return true; |
| 47 | + } |
| 48 | + |
| 49 | + public function getTokenSalt() { |
| 50 | + $params = $this->extractRequestParams(); |
| 51 | + return 'deletecontest' . implode( '|', $params['ids'] ); |
| 52 | + } |
| 53 | + |
| 54 | + public function mustBePosted() { |
| 55 | + return true; |
| 56 | + } |
| 57 | + |
| 58 | + public function getAllowedParams() { |
| 59 | + return array( |
| 60 | + 'ids' => array( |
| 61 | + ApiBase::PARAM_TYPE => 'integer', |
| 62 | + ApiBase::PARAM_REQUIRED => true, |
| 63 | + ApiBase::PARAM_ISMULTI => true, |
| 64 | + ), |
| 65 | + 'token' => null, |
| 66 | + ); |
| 67 | + } |
| 68 | + |
| 69 | + public function getParamDescription() { |
| 70 | + return array( |
| 71 | + 'ids' => 'The IDs of the contests to delete', |
| 72 | + 'token' => 'Edit token, salted with the contest id', |
| 73 | + ); |
| 74 | + } |
| 75 | + |
| 76 | + public function getDescription() { |
| 77 | + return array( |
| 78 | + 'API module for deleting contests.' |
| 79 | + ); |
| 80 | + } |
| 81 | + |
| 82 | + public function getPossibleErrors() { |
| 83 | + return array_merge( parent::getPossibleErrors(), array( |
| 84 | + array( 'missingparam', 'ids' ), |
| 85 | + ) ); |
| 86 | + } |
| 87 | + |
| 88 | + protected function getExamples() { |
| 89 | + return array( |
| 90 | + 'api.php?action=deletecontest&ids=42', |
| 91 | + 'api.php?action=deletecontest&ids=4|2', |
| 92 | + ); |
| 93 | + } |
| 94 | + |
| 95 | + public function getVersion() { |
| 96 | + return __CLASS__ . ': $Id$'; |
| 97 | + } |
| 98 | + |
| 99 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/api/ApiDeleteContest.php |
___________________________________________________________________ |
Added: svn:keywords |
1 | 100 | + Id |
Added: svn:eol-style |
2 | 101 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/api/ApiContestQuery.php |
— | — | @@ -0,0 +1,231 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Base class for API query modules that return results using a |
| 6 | + * ContestDBObject deriving class. |
| 7 | + * |
| 8 | + * @since 0.1 |
| 9 | + * |
| 10 | + * @file ApiContestQuery.php |
| 11 | + * @ingroup Contest |
| 12 | + * @ingroup API |
| 13 | + * |
| 14 | + * @licence GNU GPL v3+ |
| 15 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 16 | + */ |
| 17 | +abstract class ApiContestQuery extends ApiQueryBase { |
| 18 | + |
| 19 | + /** |
| 20 | + * Returns the class specific info. |
| 21 | + * * class: name of the ContestDBObject deriving class (ie Contest) |
| 22 | + * * item: item name (ie contest) |
| 23 | + * * set: item set name (ie contests) |
| 24 | + * |
| 25 | + * @since 0.1 |
| 26 | + * |
| 27 | + * @return array of string |
| 28 | + */ |
| 29 | + protected abstract function getClassInfo(); |
| 30 | + |
| 31 | + /** |
| 32 | + * Returns an instance of the ContestDBObject deriving class. |
| 33 | + * Once PHP 5.3 becomes an acceptable requirement, we |
| 34 | + * can get rid of this silly hack and simply return the class |
| 35 | + * name (since all methods we need ought to be static in PHP >= 5.3). |
| 36 | + * |
| 37 | + * @since 0.1 |
| 38 | + * |
| 39 | + * @return ContestDBObject |
| 40 | + */ |
| 41 | + protected function getClass() { |
| 42 | + $className = $this->getClassInfo(); |
| 43 | + $className = $className['class']; |
| 44 | + return $className::s(); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * Get the parameters, find out what the conditions for the query are, |
| 49 | + * run it, and add the results. |
| 50 | + * |
| 51 | + * @since 0.1 |
| 52 | + */ |
| 53 | + public function execute() { |
| 54 | + $params = $this->getParams(); |
| 55 | + |
| 56 | + if ( count( $params['props'] ) > 0 ) { |
| 57 | + $results = $this->getResults( $params, $this->getConditions( $params ) ); |
| 58 | + $this->addResults( $params, $results ); |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Get the request parameters, handle the * value for the props param |
| 64 | + * and remove all params set to null (ie those that are not actually provided). |
| 65 | + * |
| 66 | + * @since 0.1 |
| 67 | + * |
| 68 | + * @return array |
| 69 | + */ |
| 70 | + protected function getParams() { |
| 71 | + // Get the requests parameters. |
| 72 | + $params = $this->extractRequestParams(); |
| 73 | + |
| 74 | + $starPropPosition = array_search( '*', $params['props'] ); |
| 75 | + |
| 76 | + if ( $starPropPosition !== false ) { |
| 77 | + unset( $params['props'][$starPropPosition] ); |
| 78 | + $params['props'] = array_merge( $params['props'], $this->getClass()->getFieldNames() ); |
| 79 | + } |
| 80 | + |
| 81 | + return array_filter( $params, create_function( '$p', 'return isset( $p );' ) ); |
| 82 | + } |
| 83 | + |
| 84 | + /** |
| 85 | + * Get the conditions for the query. These will be provided as |
| 86 | + * regular parameters, together with limit, props, continue, |
| 87 | + * and possibly others which we need to get rid off. |
| 88 | + * |
| 89 | + * @since 0.1 |
| 90 | + * |
| 91 | + * @param array $params |
| 92 | + * |
| 93 | + * @return array |
| 94 | + */ |
| 95 | + protected function getConditions( array $params ) { |
| 96 | + $conditions = array(); |
| 97 | + |
| 98 | + foreach ( $params as $name => $value ) { |
| 99 | + if ( $this->getClass()->canHasField( $name ) ) { |
| 100 | + $conditions[$name] = $value; |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + return $conditions; |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * Get the actual results. |
| 109 | + * |
| 110 | + * @since 0.1 |
| 111 | + * |
| 112 | + * @param array $params |
| 113 | + * @param array $conditions |
| 114 | + * |
| 115 | + * @return array of ContestDBClass |
| 116 | + */ |
| 117 | + protected function getResults( array $params, array $conditions ) { |
| 118 | + return $this->getClass()->select( |
| 119 | + $params['props'], |
| 120 | + $conditions, |
| 121 | + array( |
| 122 | + 'LIMIT' => $params['limit'] + 1, |
| 123 | + 'ORDER BY' => $this->getClass()->getPrefixedField( 'id' ) . ' ASC' |
| 124 | + ) |
| 125 | + ); |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * Serialize the results and add them to the result object. |
| 130 | + * |
| 131 | + * @since 0.1 |
| 132 | + * |
| 133 | + * @param array $params |
| 134 | + * @param array $results |
| 135 | + */ |
| 136 | + protected function addResults( array $params, array /* of ContestDBObject */ $results ) { |
| 137 | + $serializedResults = array(); |
| 138 | + $count = 0; |
| 139 | + |
| 140 | + foreach ( $results as $result ) { |
| 141 | + if ( ++$count > $params['limit'] ) { |
| 142 | + // We've reached the one extra which shows that |
| 143 | + // there are additional pages to be had. Stop here... |
| 144 | + $this->setContinueEnumParameter( 'continue', $result->getId() ); |
| 145 | + break; |
| 146 | + } |
| 147 | + |
| 148 | + $serializedResults[] = $result->toArray( $params['props'] ); |
| 149 | + } |
| 150 | + |
| 151 | + $this->setIndexedTagNames( $serializedResults ); |
| 152 | + $this->addSerializedResults( $serializedResults ); |
| 153 | + } |
| 154 | + |
| 155 | + /** |
| 156 | + * Set the tag names for formats such as XML. |
| 157 | + * |
| 158 | + * @since 0.1 |
| 159 | + * |
| 160 | + * @param array $serializedResults |
| 161 | + */ |
| 162 | + protected function setIndexedTagNames( array &$serializedResults ) { |
| 163 | + $classInfo = $this->getClassInfo(); |
| 164 | + $this->getResult()->setIndexedTagName( $serializedResults, $classInfo['item'] ); |
| 165 | + } |
| 166 | + |
| 167 | + /** |
| 168 | + * Add the serialized results to the result object. |
| 169 | + * |
| 170 | + * @since 0.1 |
| 171 | + * |
| 172 | + * @param array $serializedResults |
| 173 | + */ |
| 174 | + protected function addSerializedResults( array $serializedResults ) { |
| 175 | + $classInfo = $this->getClassInfo(); |
| 176 | + |
| 177 | + $this->getResult()->addValue( |
| 178 | + null, |
| 179 | + $classInfo['set'], |
| 180 | + $serializedResults |
| 181 | + ); |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * (non-PHPdoc) |
| 186 | + * @see includes/api/ApiBase#getAllowedParams() |
| 187 | + * @return array |
| 188 | + */ |
| 189 | + public function getAllowedParams() { |
| 190 | + $params = array ( |
| 191 | + 'props' => array( |
| 192 | + ApiBase::PARAM_TYPE => array_merge( $this->getClass()->getFieldNames(), array( '*' ) ), |
| 193 | + ApiBase::PARAM_ISMULTI => true, |
| 194 | + ApiBase::PARAM_DFLT => '*' |
| 195 | + ), |
| 196 | + 'limit' => array( |
| 197 | + ApiBase::PARAM_DFLT => 20, |
| 198 | + ApiBase::PARAM_TYPE => 'limit', |
| 199 | + ApiBase::PARAM_MIN => 1, |
| 200 | + ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, |
| 201 | + ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 |
| 202 | + ), |
| 203 | + 'continue' => null, |
| 204 | + ); |
| 205 | + |
| 206 | + return array_merge( $this->getClass()->getAPIParams(), $params ); |
| 207 | + } |
| 208 | + |
| 209 | + /** |
| 210 | + * (non-PHPdoc) |
| 211 | + * @see includes/api/ApiBase#getParamDescription() |
| 212 | + */ |
| 213 | + public function getParamDescription() { |
| 214 | + $descs = array ( |
| 215 | + 'props' => 'Fields to query', |
| 216 | + 'continue' => 'Offset number from where to continue the query', |
| 217 | + 'limit' => 'Max amount of rows to return', |
| 218 | + ); |
| 219 | + |
| 220 | + return array_merge( $this->getClass()->getFieldDescriptions(), $descs ); |
| 221 | + } |
| 222 | + |
| 223 | + /** |
| 224 | + * (non-PHPdoc) |
| 225 | + * @see includes/api/ApiBase#getPossibleErrors() |
| 226 | + */ |
| 227 | + public function getPossibleErrors() { |
| 228 | + return array_merge( parent::getPossibleErrors(), array( |
| 229 | + ) ); |
| 230 | + } |
| 231 | + |
| 232 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/api/ApiContestQuery.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 233 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/api/ApiMailContestants.php |
— | — | @@ -0,0 +1,197 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * API module to mail contestants. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ApiMailContestants.php |
| 10 | + * @ingroup Contest |
| 11 | + * @ingroup API |
| 12 | + * |
| 13 | + * @licence GNU GPL v3+ |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ApiMailContestants extends ApiBase { |
| 17 | + |
| 18 | + public function __construct( $main, $action ) { |
| 19 | + parent::__construct( $main, $action ); |
| 20 | + } |
| 21 | + |
| 22 | + // TODO |
| 23 | + public function execute() { |
| 24 | + global $wgUser; |
| 25 | + |
| 26 | + if ( !$wgUser->isAllowed( 'contestadmin' ) || $wgUser->isBlocked() ) { |
| 27 | + $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 28 | + } |
| 29 | + |
| 30 | + $params = $this->extractRequestParams(); |
| 31 | + |
| 32 | + $everythingOk = true; |
| 33 | + |
| 34 | + $contestIds = is_null( $params['contestids'] ) ? array() : $params['contestids']; |
| 35 | + $challengeIds = is_null( $params['challengeids'] ) ? array() : $params['challengeids']; |
| 36 | + |
| 37 | + if ( !is_null( $params['challengetitles'] ) ) { |
| 38 | + $challenges = ContestChallenge::s()->select( 'id', array( 'title' => $params['challengetitles'] ) ); |
| 39 | + |
| 40 | + if ( $challenges === false ) { |
| 41 | + // TODO: error |
| 42 | + } |
| 43 | + |
| 44 | + foreach ( $challenges as /* ContestChallenge */ $challenge ) { |
| 45 | + $challengeIds[] = $challenge->getId(); |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + if ( !is_null( $params['contestnames'] ) ) { |
| 50 | + $contests = Contest::s()->select( 'id', array( 'name' => $params['contestnames'] ) ); |
| 51 | + |
| 52 | + if ( $contests === false ) { |
| 53 | + // TODO: error |
| 54 | + } |
| 55 | + |
| 56 | + foreach ( $contests as /* Contest */ $contest ) { |
| 57 | + $contestIds[] = $contest->getId(); |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + $conditions = array(); |
| 62 | + |
| 63 | + if ( count( $contestIds ) > 0 ) { |
| 64 | + $conditions['contest_id'] = $contestIds; |
| 65 | + } |
| 66 | + |
| 67 | + if ( count( $challengeIds ) > 0 ) { |
| 68 | + $conditions['challenge_id'] = $challengeIds; |
| 69 | + } |
| 70 | + |
| 71 | + if ( !is_null( $params['ids'] ) && count( $params['ids'] ) > 0 ) { |
| 72 | + $conditions['id'] = $params['ids']; |
| 73 | + } |
| 74 | + |
| 75 | + $contestants = ContestContestant::s()->select( 'email', $conditions ); |
| 76 | + |
| 77 | + if ( $contestants !== false && count( $contestants ) > 0 ) { |
| 78 | + $setSize = ContestSettings::get( 'reminderJobSize' ); |
| 79 | + $limit = count( $contestants ) - $setSize; |
| 80 | + |
| 81 | + for ( $i = 0; $i <= $limit; $i += $setSize ) { |
| 82 | + $this->createReminderJob( array_splice( $contestants, $i, $setSize ) ); |
| 83 | + } |
| 84 | + } |
| 85 | + else { |
| 86 | + $everythingOk = false; |
| 87 | + } |
| 88 | + |
| 89 | + $this->getResult()->addValue( |
| 90 | + null, |
| 91 | + 'success', |
| 92 | + $everythingOk |
| 93 | + ); |
| 94 | + |
| 95 | + if ( $everythingOk ) { |
| 96 | + $this->getResult()->addValue( |
| 97 | + null, |
| 98 | + 'contestantcount', |
| 99 | + count( $contestants ) |
| 100 | + ); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + protected function createReminderJob( array /* of ContestContestant */ $contestants ) { |
| 105 | + $job = new ContestReminderJob( |
| 106 | + Title::newMainPage(), // WTF does this require a title for?? |
| 107 | + array( |
| 108 | + 'contest' => $contestants[0]->getContest(), |
| 109 | + 'contestants' => $contestants |
| 110 | + ) |
| 111 | + ); |
| 112 | + $job->insert(); |
| 113 | + } |
| 114 | + |
| 115 | + public function needsToken() { |
| 116 | + return true; |
| 117 | + } |
| 118 | + |
| 119 | + public function mustBePosted() { |
| 120 | + return true; |
| 121 | + } |
| 122 | + |
| 123 | + public function getAllowedParams() { |
| 124 | + return array( |
| 125 | +// 'page' => array( |
| 126 | +// ApiBase::PARAM_TYPE => 'string', |
| 127 | +// ApiBase::PARAM_REQUIRED => true, |
| 128 | +// ApiBase::PARAM_ISMULTI => false, |
| 129 | +// ), |
| 130 | + 'ids' => array( |
| 131 | + ApiBase::PARAM_TYPE => 'integer', |
| 132 | + ApiBase::PARAM_REQUIRED => false, |
| 133 | + ApiBase::PARAM_ISMULTI => true, |
| 134 | + ), |
| 135 | + 'contestids' => array( |
| 136 | + ApiBase::PARAM_TYPE => 'integer', |
| 137 | + ApiBase::PARAM_REQUIRED => false, |
| 138 | + ApiBase::PARAM_ISMULTI => true, |
| 139 | + ), |
| 140 | + 'contestnames' => array( |
| 141 | + ApiBase::PARAM_TYPE => 'string', |
| 142 | + ApiBase::PARAM_REQUIRED => false, |
| 143 | + ApiBase::PARAM_ISMULTI => true, |
| 144 | + ), |
| 145 | + 'challengeids' => array( |
| 146 | + ApiBase::PARAM_TYPE => 'integer', |
| 147 | + ApiBase::PARAM_REQUIRED => false, |
| 148 | + ApiBase::PARAM_ISMULTI => true, |
| 149 | + ), |
| 150 | + 'challengetitles' => array( |
| 151 | + ApiBase::PARAM_TYPE => 'string', |
| 152 | + ApiBase::PARAM_REQUIRED => false, |
| 153 | + ApiBase::PARAM_ISMULTI => true, |
| 154 | + ), |
| 155 | + 'token' => null, |
| 156 | + ); |
| 157 | + } |
| 158 | + |
| 159 | + public function getParamDescription() { |
| 160 | + return array( |
| 161 | +// 'page' => 'Name of the page from which to pull content for the email body', |
| 162 | + 'ids' => 'The IDs of the contestants to mail', |
| 163 | + 'contestids' => 'The IDs of the contests where of the contestants should be mailed', |
| 164 | + 'contestnames' => 'The names of the contests where of the contestants should be mailed', |
| 165 | + 'challengeids' => 'The IDs of the challenges where of the contestants should be mailed', |
| 166 | + 'challengetitles' => 'The titles of the challenges where of the contestants should be mailed', |
| 167 | + 'token' => 'Edit token', |
| 168 | + ); |
| 169 | + } |
| 170 | + |
| 171 | + public function getDescription() { |
| 172 | + return array( |
| 173 | + 'API module for mailing contestants. Selection criteria will be joined with AND, |
| 174 | + except for the challange ids/titles and contest ids/names pairs, which will be joined wit OR.' |
| 175 | + ); |
| 176 | + } |
| 177 | + |
| 178 | + public function getPossibleErrors() { |
| 179 | + return array_merge( parent::getPossibleErrors(), array( |
| 180 | + array( 'missingparam', 'ids' ), |
| 181 | + ) ); |
| 182 | + } |
| 183 | + |
| 184 | + protected function getExamples() { |
| 185 | + return array( |
| 186 | + 'api.php?action=mailcontestants&ids=42', |
| 187 | + 'api.php?action=mailcontestants&ids=4|2', |
| 188 | + 'api.php?action=mailcontestants&contestids=42', |
| 189 | + 'api.php?action=mailcontestants&contestnames=Weekend of Code', |
| 190 | + 'api.php?action=mailcontestants&challengetitles=foo|bar|baz', |
| 191 | + ); |
| 192 | + } |
| 193 | + |
| 194 | + public function getVersion() { |
| 195 | + return __CLASS__ . ': $Id$'; |
| 196 | + } |
| 197 | + |
| 198 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/api/ApiMailContestants.php |
___________________________________________________________________ |
Added: svn:keywords |
1 | 199 | + Id |
Added: svn:eol-style |
2 | 200 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryContests.php |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * API module to get a list of contests. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ApiQueryContests.php |
| 10 | + * @ingroup Contest |
| 11 | + * @ingroup API |
| 12 | + * |
| 13 | + * @licence GNU GPL v3+ |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ApiQueryContests extends ApiContestQuery { |
| 17 | + |
| 18 | + /** |
| 19 | + * (non-PHPdoc) |
| 20 | + * @see ApiContestQuery::getClassInfo() |
| 21 | + * @return array |
| 22 | + */ |
| 23 | + protected function getClassInfo() { |
| 24 | + return array( |
| 25 | + 'class' => 'Contest', |
| 26 | + 'item' => 'contest', |
| 27 | + 'set' => 'contests', |
| 28 | + ); |
| 29 | + } |
| 30 | + |
| 31 | + public function __construct( $main, $action ) { |
| 32 | + parent::__construct( $main, $action, 'co' ); |
| 33 | + } |
| 34 | + |
| 35 | + /** |
| 36 | + * Handle the API request. |
| 37 | + * Checks for access rights and then let's the parent method do the actual work. |
| 38 | + */ |
| 39 | + public function execute() { |
| 40 | + global $wgUser; |
| 41 | + |
| 42 | + if ( !$wgUser->isAllowed( 'contestadmin' ) || $wgUser->isBlocked() ) { |
| 43 | + $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 44 | + } |
| 45 | + |
| 46 | + parent::execute(); |
| 47 | + } |
| 48 | + |
| 49 | + /** |
| 50 | + * (non-PHPdoc) |
| 51 | + * @see includes/api/ApiBase#getDescription() |
| 52 | + */ |
| 53 | + public function getDescription() { |
| 54 | + return 'API module for querying contests'; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * (non-PHPdoc) |
| 59 | + * @see includes/api/ApiBase#getExamples() |
| 60 | + */ |
| 61 | + protected function getExamples() { |
| 62 | + return array ( |
| 63 | + 'api.php?action=query&list=contests&coprops=id|name', |
| 64 | + 'api.php?action=query&list=contests&costatus=1', |
| 65 | + ); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * (non-PHPdoc) |
| 70 | + * @see includes/api/ApiBase#getVersion() |
| 71 | + */ |
| 72 | + public function getVersion() { |
| 73 | + return __CLASS__ . ': $Id$'; |
| 74 | + } |
| 75 | + |
| 76 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryContests.php |
___________________________________________________________________ |
Added: svn:keywords |
1 | 77 | + Id |
Added: svn:eol-style |
2 | 78 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryChallenges.php |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * API module to get a list of contest challenges. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ApiQueryChallenges.php |
| 10 | + * @ingroup Contest |
| 11 | + * @ingroup API |
| 12 | + * |
| 13 | + * @licence GNU GPL v3+ |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ApiQueryChallenges extends ApiContestQuery { |
| 17 | + |
| 18 | + /** |
| 19 | + * (non-PHPdoc) |
| 20 | + * @see ApiContestQuery::getClassInfo() |
| 21 | + * @return array |
| 22 | + */ |
| 23 | + protected function getClassInfo() { |
| 24 | + return array( |
| 25 | + 'class' => 'ContestChallenge', |
| 26 | + 'item' => 'challenge', |
| 27 | + 'set' => 'challenges', |
| 28 | + ); |
| 29 | + } |
| 30 | + |
| 31 | + public function __construct( $main, $action ) { |
| 32 | + parent::__construct( $main, $action, 'ch' ); |
| 33 | + } |
| 34 | + |
| 35 | + /** |
| 36 | + * Handle the API request. |
| 37 | + * Checks for access rights and then let's the parent method do the actual work. |
| 38 | + */ |
| 39 | + public function execute() { |
| 40 | + global $wgUser; |
| 41 | + |
| 42 | + if ( !$wgUser->isAllowed( 'contestadmin' ) || $wgUser->isBlocked() ) { |
| 43 | + $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 44 | + } |
| 45 | + |
| 46 | + parent::execute(); |
| 47 | + } |
| 48 | + |
| 49 | + /** |
| 50 | + * (non-PHPdoc) |
| 51 | + * @see includes/api/ApiBase#getDescription() |
| 52 | + */ |
| 53 | + public function getDescription() { |
| 54 | + return 'API module for querying contest challenges'; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * (non-PHPdoc) |
| 59 | + * @see includes/api/ApiBase#getExamples() |
| 60 | + */ |
| 61 | + protected function getExamples() { |
| 62 | + return array ( |
| 63 | + 'api.php?action=query&list=challenges&chprops=title|text', |
| 64 | + 'api.php?action=query&list=challenges&chcontestid=42&chprops=id|contest_id|title', |
| 65 | + ); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * (non-PHPdoc) |
| 70 | + * @see includes/api/ApiBase#getVersion() |
| 71 | + */ |
| 72 | + public function getVersion() { |
| 73 | + return __CLASS__ . ': $Id$'; |
| 74 | + } |
| 75 | + |
| 76 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryChallenges.php |
___________________________________________________________________ |
Added: svn:keywords |
1 | 77 | + Id |
Added: svn:eol-style |
2 | 78 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryContestComments.php |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * API module to get a list of commets. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ApiQueryContestComments.php |
| 10 | + * @ingroup Contest |
| 11 | + * @ingroup API |
| 12 | + * |
| 13 | + * @licence GNU GPL v3+ |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ApiQueryContestComments extends ApiContestQuery { |
| 17 | + |
| 18 | + /** |
| 19 | + * (non-PHPdoc) |
| 20 | + * @see ApiContestQuery::getClassInfo() |
| 21 | + * @return array |
| 22 | + */ |
| 23 | + protected function getClassInfo() { |
| 24 | + return array( |
| 25 | + 'class' => 'ContestComment', |
| 26 | + 'item' => 'comment', |
| 27 | + 'set' => 'comments', |
| 28 | + ); |
| 29 | + } |
| 30 | + |
| 31 | + public function __construct( $main, $action ) { |
| 32 | + parent::__construct( $main, $action, 'coco' ); |
| 33 | + } |
| 34 | + |
| 35 | + /** |
| 36 | + * Handle the API request. |
| 37 | + * Checks for access rights and then let's the parent method do the actual work. |
| 38 | + */ |
| 39 | + public function execute() { |
| 40 | + global $wgUser; |
| 41 | + |
| 42 | + if ( !$wgUser->isAllowed( 'contestjudge' ) || $wgUser->isBlocked() ) { |
| 43 | + $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 44 | + } |
| 45 | + |
| 46 | + parent::execute(); |
| 47 | + } |
| 48 | + |
| 49 | + /** |
| 50 | + * (non-PHPdoc) |
| 51 | + * @see includes/api/ApiBase#getDescription() |
| 52 | + */ |
| 53 | + public function getDescription() { |
| 54 | + return 'API module for querying contest comments'; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * (non-PHPdoc) |
| 59 | + * @see includes/api/ApiBase#getExamples() |
| 60 | + */ |
| 61 | + protected function getExamples() { |
| 62 | + return array ( |
| 63 | + 'api.php?action=query&list=contestcomments&cocoprops=id|user_id|contestant_id|text', |
| 64 | + 'api.php?action=query&list=contestcomments&cocoprops=id|text&cocouser_id=42', |
| 65 | + ); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * (non-PHPdoc) |
| 70 | + * @see includes/api/ApiBase#getVersion() |
| 71 | + */ |
| 72 | + public function getVersion() { |
| 73 | + return __CLASS__ . ': $Id$'; |
| 74 | + } |
| 75 | + |
| 76 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryContestComments.php |
___________________________________________________________________ |
Added: svn:keywords |
1 | 77 | + Id |
Added: svn:eol-style |
2 | 78 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryContestants.php |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * API module to get a list of contestants. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file ApiQueryContestants.php |
| 10 | + * @ingroup Contest |
| 11 | + * @ingroup API |
| 12 | + * |
| 13 | + * @licence GNU GPL v3+ |
| 14 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 15 | + */ |
| 16 | +class ApiQueryContestants extends ApiContestQuery { |
| 17 | + |
| 18 | + /** |
| 19 | + * (non-PHPdoc) |
| 20 | + * @see ApiContestQuery::getClassInfo() |
| 21 | + * @return array |
| 22 | + */ |
| 23 | + protected function getClassInfo() { |
| 24 | + return array( |
| 25 | + 'class' => 'ContestContestant', |
| 26 | + 'item' => 'contestant', |
| 27 | + 'set' => 'contestants', |
| 28 | + ); |
| 29 | + } |
| 30 | + |
| 31 | + public function __construct( $main, $action ) { |
| 32 | + parent::__construct( $main, $action, 'ct' ); |
| 33 | + } |
| 34 | + |
| 35 | + /** |
| 36 | + * Handle the API request. |
| 37 | + * Checks for access rights and then let's the parent method do the actual work. |
| 38 | + */ |
| 39 | + public function execute() { |
| 40 | + global $wgUser; |
| 41 | + |
| 42 | + if ( !$wgUser->isAllowed( 'contestadmin' ) || $wgUser->isBlocked() ) { |
| 43 | + $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 44 | + } |
| 45 | + |
| 46 | + parent::execute(); |
| 47 | + } |
| 48 | + |
| 49 | + /** |
| 50 | + * (non-PHPdoc) |
| 51 | + * @see includes/api/ApiBase#getDescription() |
| 52 | + */ |
| 53 | + public function getDescription() { |
| 54 | + return 'API module for querying contestants'; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * (non-PHPdoc) |
| 59 | + * @see includes/api/ApiBase#getExamples() |
| 60 | + */ |
| 61 | + protected function getExamples() { |
| 62 | + return array ( |
| 63 | + 'api.php?action=query&list=contestants&ctprops=id|user_id|contest_id|rating', |
| 64 | + 'api.php?action=query&list=contestants&ctprops=id|rating&ctcontest_id=42', |
| 65 | + ); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * (non-PHPdoc) |
| 70 | + * @see includes/api/ApiBase#getVersion() |
| 71 | + */ |
| 72 | + public function getVersion() { |
| 73 | + return __CLASS__ . ': $Id$'; |
| 74 | + } |
| 75 | + |
| 76 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/api/ApiQueryContestants.php |
___________________________________________________________________ |
Added: svn:keywords |
1 | 77 | + Id |
Added: svn:eol-style |
2 | 78 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.submission.js |
— | — | @@ -0,0 +1,19 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +(function( $, mw ) { |
| 11 | + |
| 12 | + $( document ).ready( function() { |
| 13 | + |
| 14 | + $( '.mw-htmlform-submit' ).button(); |
| 15 | + |
| 16 | + $( '.contest-submission' ).contestSubmission(); |
| 17 | + |
| 18 | + } ); |
| 19 | + |
| 20 | +})( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.submission.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 21 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.welcome.js |
— | — | @@ -0,0 +1,40 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +(function( $, mw ) { |
| 11 | + |
| 12 | + $( document ).ready( function() { |
| 13 | + |
| 14 | + $( '#contest-challenges' ).contestChallenges( |
| 15 | + mw.config.get( 'ContestChallenges' ), |
| 16 | + mw.config.get( 'ContestConfig' ) |
| 17 | + ); |
| 18 | + |
| 19 | + $rules = $( '#contest-rules' ); |
| 20 | + |
| 21 | + $div = $( '<div />' ).attr( { |
| 22 | + 'style': 'display:none' |
| 23 | + } ).html( $( '<div />' ).attr( { 'id': 'contest-rules-div' } ).html( $rules.attr( 'data-rules' ) ) ); |
| 24 | + |
| 25 | + // TODO: fix very ugly message construction. |
| 26 | + $a = $( '<a />' ).text( mw.msg( 'contest-welcome-rules-link' ) ).attr( { 'href': '#contest-rules-div' } ); |
| 27 | + $p = $( '<p />' ).text( mw.msg( 'contest-welcome-rules' ) + ' ' ).append( $a ).append( '.' ); |
| 28 | + |
| 29 | + $rules.html( $p ).append( $div ); |
| 30 | + |
| 31 | + $a.fancybox( { |
| 32 | + 'width' : '85%', |
| 33 | + 'height' : '85%', |
| 34 | + 'transitionIn' : 'none', |
| 35 | + 'transitionOut' : 'none', |
| 36 | + 'type' : 'inline', |
| 37 | + 'autoDimensions': false |
| 38 | + } ); |
| 39 | + } ); |
| 40 | + |
| 41 | +})( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.welcome.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 42 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.contestant.pager.css |
— | — | @@ -0,0 +1,11 @@ |
| 2 | +/** |
| 3 | + * CSS for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +.contestant-row td:hover { |
| 11 | + cursor: pointer; |
| 12 | +} |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.contestant.pager.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 13 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.contestSubmission.js |
— | — | @@ -0,0 +1,115 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +(function( $, mw ) { |
| 11 | + |
| 12 | + /** |
| 13 | + * Regex text escaping function. |
| 14 | + * Borrowed from http://simonwillison.net/2006/Jan/20/escape/ |
| 15 | + */ |
| 16 | + RegExp.escape = function( text ) { |
| 17 | + if ( !arguments.callee.sRE ) { |
| 18 | + var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' ]; |
| 19 | + arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' ); |
| 20 | + } |
| 21 | + return text.replace(arguments.callee.sRE, '\\$1'); |
| 22 | + } |
| 23 | + |
| 24 | + $.fn.contestSubmission = function() { |
| 25 | + var _this = this; |
| 26 | + var $this = $( this ); |
| 27 | + |
| 28 | + this.config = {}; |
| 29 | + this.status = {}; |
| 30 | + |
| 31 | + this.input = null; |
| 32 | + this.label = null; |
| 33 | + |
| 34 | + this.getValue = function() { |
| 35 | + return this.input.val(); |
| 36 | + } |
| 37 | + |
| 38 | + this.getDomains = function() { |
| 39 | + return this.config.domains; |
| 40 | + }; |
| 41 | + |
| 42 | + this.validate = function() { |
| 43 | + var domains = _this.getDomains(); |
| 44 | + |
| 45 | + for ( var i = domains.length - 1; i >= 0; i-- ) { |
| 46 | + var regex = new RegExp( "^https?://(([a-z0-9]+)\\.)?" + RegExp.escape( domains[i] ) + "/(.*)?$", "gi" ); |
| 47 | + if ( regex.test( this.getValue() ) ) { |
| 48 | + return true; |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + return false; |
| 53 | + }; |
| 54 | + |
| 55 | + this.showStatus = function() { |
| 56 | + if ( _this.status.valid ) { |
| 57 | + _this.input.removeClass( 'error' ); |
| 58 | + } |
| 59 | + else { |
| 60 | + _this.input.addClass( 'error' ); |
| 61 | + } |
| 62 | + }; |
| 63 | + |
| 64 | + this.onValueChanged = function() { |
| 65 | + _this.status.valid = _this.validate(); |
| 66 | + _this.showStatus(); |
| 67 | + }; |
| 68 | + |
| 69 | + this.setup = function() { |
| 70 | + var message = $this.attr( 'data-value' ) === '' ? 'contest-submission-new-submission' : 'contest-submission-current-submission'; |
| 71 | + var domainLinks = []; |
| 72 | + |
| 73 | + for ( var i = this.config.domains.length - 1; i >= 0; i-- ) { |
| 74 | + var link = $( '<a />' ).text( this.config.domains[i] ).attr( { |
| 75 | + 'href': 'http://' + this.config.domains[i], |
| 76 | + 'target': 'blank' |
| 77 | + } ); |
| 78 | + domainLinks.push( $( '<div />' ).html( link ).html() ); |
| 79 | + } |
| 80 | + |
| 81 | + var links = $( '<span />' ).html( '' ); |
| 82 | + |
| 83 | + this.label = $( '<label style="display:block" />' ).attr( { |
| 84 | + 'for': this.config.name, |
| 85 | + } ).text( mw.msg( message ) ).append( |
| 86 | + $( '<br />' ), |
| 87 | + mw.msg( 'contest-submission-domains', domainLinks.join( ', ' ) ) |
| 88 | + ); |
| 89 | + |
| 90 | + this.input = $( '<input />' ).attr( { |
| 91 | + 'type': 'text', |
| 92 | + 'value': $this.attr( 'data-value' ), |
| 93 | + 'name': this.config.name, |
| 94 | + 'size': 45, |
| 95 | + 'id': this.config.name |
| 96 | + } ); |
| 97 | + |
| 98 | + this.html( this.label ); |
| 99 | + this.append( this.input ); |
| 100 | + |
| 101 | + this.input.keyup( this.onValueChanged ); |
| 102 | + }; |
| 103 | + |
| 104 | + this.getConfig = function() { |
| 105 | + this.config.name = $this.attr( 'data-name' ); |
| 106 | + this.config.domains = $this.attr( 'data-domains' ).split( '|' ); |
| 107 | + }; |
| 108 | + |
| 109 | + this.getConfig(); |
| 110 | + this.setup(); |
| 111 | + this.onValueChanged(); |
| 112 | + |
| 113 | + return this; |
| 114 | + }; |
| 115 | + |
| 116 | +})( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.contestSubmission.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 117 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/images/callout.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/images/callout.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 118 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/images/arrow.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/images/arrow.png |
___________________________________________________________________ |
Added: svn:mime-type |
3 | 119 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/images/arrow-hover.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/images/arrow-hover.png |
___________________________________________________________________ |
Added: svn:mime-type |
4 | 120 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/images/box-sprite.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/images/box-sprite.png |
___________________________________________________________________ |
Added: svn:mime-type |
5 | 121 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/images/box-top-sprite.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/images/box-top-sprite.png |
___________________________________________________________________ |
Added: svn:mime-type |
6 | 122 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/images/box-bottom-sprite.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/images/box-bottom-sprite.png |
___________________________________________________________________ |
Added: svn:mime-type |
7 | 123 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.contestant.css |
— | — | @@ -0,0 +1,24 @@ |
| 2 | +/** |
| 3 | + * CSS for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +.contestant-comment { |
| 11 | + border: solid 1px #aaaab3; |
| 12 | + padding: 0; |
| 13 | + background: #eee; |
| 14 | + margin-bottom: 8px; |
| 15 | + margin-left: 0px; |
| 16 | +} |
| 17 | + |
| 18 | +.contestant-comment-meta { |
| 19 | + padding: 4px 8px; |
| 20 | +} |
| 21 | + |
| 22 | +.contestant-comment-text { |
| 23 | + background: white; |
| 24 | + padding: 16px; |
| 25 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.contestant.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 26 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.contestant.pager.js |
— | — | @@ -0,0 +1,19 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +(function( $, mw ) { |
| 11 | + |
| 12 | + $( document ).ready( function() { |
| 13 | + |
| 14 | + $( '.contestant-row' ).click( function() { |
| 15 | + window.location = $( this ).attr( 'data-contestant-target' ); |
| 16 | + } ); |
| 17 | + |
| 18 | + } ); |
| 19 | + |
| 20 | +})( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.contestant.pager.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 21 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_sw.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_sw.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 22 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_nav_left.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_nav_left.png |
___________________________________________________________________ |
Added: svn:mime-type |
3 | 23 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancybox-x.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancybox-x.png |
___________________________________________________________________ |
Added: svn:mime-type |
4 | 24 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancybox-y.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancybox-y.png |
___________________________________________________________________ |
Added: svn:mime-type |
5 | 25 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.fancybox-1.3.4.js |
— | — | @@ -0,0 +1,1156 @@ |
| 2 | +/* |
| 3 | + * FancyBox - jQuery Plugin |
| 4 | + * Simple and fancy lightbox alternative |
| 5 | + * |
| 6 | + * Examples and documentation at: http://fancybox.net |
| 7 | + * |
| 8 | + * Copyright (c) 2008 - 2010 Janis Skarnelis |
| 9 | + * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. |
| 10 | + * |
| 11 | + * Version: 1.3.4 (11/11/2010) |
| 12 | + * Requires: jQuery v1.3+ |
| 13 | + * |
| 14 | + * Dual licensed under the MIT and GPL licenses: |
| 15 | + * http://www.opensource.org/licenses/mit-license.php |
| 16 | + * http://www.gnu.org/licenses/gpl.html |
| 17 | + */ |
| 18 | + |
| 19 | +;(function($) { |
| 20 | + var tmp, loading, overlay, wrap, outer, content, close, title, nav_left, nav_right, |
| 21 | + |
| 22 | + selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [], |
| 23 | + |
| 24 | + ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i, |
| 25 | + |
| 26 | + loadingTimer, loadingFrame = 1, |
| 27 | + |
| 28 | + titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('<div/>')[0], { prop: 0 }), |
| 29 | + |
| 30 | + isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest, |
| 31 | + |
| 32 | + /* |
| 33 | + * Private methods |
| 34 | + */ |
| 35 | + |
| 36 | + _abort = function() { |
| 37 | + loading.hide(); |
| 38 | + |
| 39 | + imgPreloader.onerror = imgPreloader.onload = null; |
| 40 | + |
| 41 | + if (ajaxLoader) { |
| 42 | + ajaxLoader.abort(); |
| 43 | + } |
| 44 | + |
| 45 | + tmp.empty(); |
| 46 | + }, |
| 47 | + |
| 48 | + _error = function() { |
| 49 | + if (false === selectedOpts.onError(selectedArray, selectedIndex, selectedOpts)) { |
| 50 | + loading.hide(); |
| 51 | + busy = false; |
| 52 | + return; |
| 53 | + } |
| 54 | + |
| 55 | + selectedOpts.titleShow = false; |
| 56 | + |
| 57 | + selectedOpts.width = 'auto'; |
| 58 | + selectedOpts.height = 'auto'; |
| 59 | + |
| 60 | + tmp.html( '<p id="fancybox-error">The requested content cannot be loaded.<br />Please try again later.</p>' ); |
| 61 | + |
| 62 | + _process_inline(); |
| 63 | + }, |
| 64 | + |
| 65 | + _start = function() { |
| 66 | + var obj = selectedArray[ selectedIndex ], |
| 67 | + href, |
| 68 | + type, |
| 69 | + title, |
| 70 | + str, |
| 71 | + emb, |
| 72 | + ret; |
| 73 | + |
| 74 | + _abort(); |
| 75 | + |
| 76 | + selectedOpts = $.extend({}, $.fn.fancybox.defaults, (typeof $(obj).data('fancybox') == 'undefined' ? selectedOpts : $(obj).data('fancybox'))); |
| 77 | + |
| 78 | + ret = selectedOpts.onStart(selectedArray, selectedIndex, selectedOpts); |
| 79 | + |
| 80 | + if (ret === false) { |
| 81 | + busy = false; |
| 82 | + return; |
| 83 | + } else if (typeof ret == 'object') { |
| 84 | + selectedOpts = $.extend(selectedOpts, ret); |
| 85 | + } |
| 86 | + |
| 87 | + title = selectedOpts.title || (obj.nodeName ? $(obj).attr('title') : obj.title) || ''; |
| 88 | + |
| 89 | + if (obj.nodeName && !selectedOpts.orig) { |
| 90 | + selectedOpts.orig = $(obj).children("img:first").length ? $(obj).children("img:first") : $(obj); |
| 91 | + } |
| 92 | + |
| 93 | + if (title === '' && selectedOpts.orig && selectedOpts.titleFromAlt) { |
| 94 | + title = selectedOpts.orig.attr('alt'); |
| 95 | + } |
| 96 | + |
| 97 | + href = selectedOpts.href || (obj.nodeName ? $(obj).attr('href') : obj.href) || null; |
| 98 | + |
| 99 | + if ((/^(?:javascript)/i).test(href) || href == '#') { |
| 100 | + href = null; |
| 101 | + } |
| 102 | + |
| 103 | + if (selectedOpts.type) { |
| 104 | + type = selectedOpts.type; |
| 105 | + |
| 106 | + if (!href) { |
| 107 | + href = selectedOpts.content; |
| 108 | + } |
| 109 | + |
| 110 | + } else if (selectedOpts.content) { |
| 111 | + type = 'html'; |
| 112 | + |
| 113 | + } else if (href) { |
| 114 | + if (href.match(imgRegExp)) { |
| 115 | + type = 'image'; |
| 116 | + |
| 117 | + } else if (href.match(swfRegExp)) { |
| 118 | + type = 'swf'; |
| 119 | + |
| 120 | + } else if ($(obj).hasClass("iframe")) { |
| 121 | + type = 'iframe'; |
| 122 | + |
| 123 | + } else if (href.indexOf("#") === 0) { |
| 124 | + type = 'inline'; |
| 125 | + |
| 126 | + } else { |
| 127 | + type = 'ajax'; |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + if (!type) { |
| 132 | + _error(); |
| 133 | + return; |
| 134 | + } |
| 135 | + |
| 136 | + if (type == 'inline') { |
| 137 | + obj = href.substr(href.indexOf("#")); |
| 138 | + type = $(obj).length > 0 ? 'inline' : 'ajax'; |
| 139 | + } |
| 140 | + |
| 141 | + selectedOpts.type = type; |
| 142 | + selectedOpts.href = href; |
| 143 | + selectedOpts.title = title; |
| 144 | + |
| 145 | + if (selectedOpts.autoDimensions) { |
| 146 | + if (selectedOpts.type == 'html' || selectedOpts.type == 'inline' || selectedOpts.type == 'ajax') { |
| 147 | + selectedOpts.width = 'auto'; |
| 148 | + selectedOpts.height = 'auto'; |
| 149 | + } else { |
| 150 | + selectedOpts.autoDimensions = false; |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + if (selectedOpts.modal) { |
| 155 | + selectedOpts.overlayShow = true; |
| 156 | + selectedOpts.hideOnOverlayClick = false; |
| 157 | + selectedOpts.hideOnContentClick = false; |
| 158 | + selectedOpts.enableEscapeButton = false; |
| 159 | + selectedOpts.showCloseButton = false; |
| 160 | + } |
| 161 | + |
| 162 | + selectedOpts.padding = parseInt(selectedOpts.padding, 10); |
| 163 | + selectedOpts.margin = parseInt(selectedOpts.margin, 10); |
| 164 | + |
| 165 | + tmp.css('padding', (selectedOpts.padding + selectedOpts.margin)); |
| 166 | + |
| 167 | + $('.fancybox-inline-tmp').unbind('fancybox-cancel').bind('fancybox-change', function() { |
| 168 | + $(this).replaceWith(content.children()); |
| 169 | + }); |
| 170 | + |
| 171 | + switch (type) { |
| 172 | + case 'html' : |
| 173 | + tmp.html( selectedOpts.content ); |
| 174 | + _process_inline(); |
| 175 | + break; |
| 176 | + |
| 177 | + case 'inline' : |
| 178 | + if ( $(obj).parent().is('#fancybox-content') === true) { |
| 179 | + busy = false; |
| 180 | + return; |
| 181 | + } |
| 182 | + |
| 183 | + $('<div class="fancybox-inline-tmp" />') |
| 184 | + .hide() |
| 185 | + .insertBefore( $(obj) ) |
| 186 | + .bind('fancybox-cleanup', function() { |
| 187 | + $(this).replaceWith(content.children()); |
| 188 | + }).bind('fancybox-cancel', function() { |
| 189 | + $(this).replaceWith(tmp.children()); |
| 190 | + }); |
| 191 | + |
| 192 | + $(obj).appendTo(tmp); |
| 193 | + |
| 194 | + _process_inline(); |
| 195 | + break; |
| 196 | + |
| 197 | + case 'image': |
| 198 | + busy = false; |
| 199 | + |
| 200 | + $.fancybox.showActivity(); |
| 201 | + |
| 202 | + imgPreloader = new Image(); |
| 203 | + |
| 204 | + imgPreloader.onerror = function() { |
| 205 | + _error(); |
| 206 | + }; |
| 207 | + |
| 208 | + imgPreloader.onload = function() { |
| 209 | + busy = true; |
| 210 | + |
| 211 | + imgPreloader.onerror = imgPreloader.onload = null; |
| 212 | + |
| 213 | + _process_image(); |
| 214 | + }; |
| 215 | + |
| 216 | + imgPreloader.src = href; |
| 217 | + break; |
| 218 | + |
| 219 | + case 'swf': |
| 220 | + selectedOpts.scrolling = 'no'; |
| 221 | + |
| 222 | + str = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"><param name="movie" value="' + href + '"></param>'; |
| 223 | + emb = ''; |
| 224 | + |
| 225 | + $.each(selectedOpts.swf, function(name, val) { |
| 226 | + str += '<param name="' + name + '" value="' + val + '"></param>'; |
| 227 | + emb += ' ' + name + '="' + val + '"'; |
| 228 | + }); |
| 229 | + |
| 230 | + str += '<embed src="' + href + '" type="application/x-shockwave-flash" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"' + emb + '></embed></object>'; |
| 231 | + |
| 232 | + tmp.html(str); |
| 233 | + |
| 234 | + _process_inline(); |
| 235 | + break; |
| 236 | + |
| 237 | + case 'ajax': |
| 238 | + busy = false; |
| 239 | + |
| 240 | + $.fancybox.showActivity(); |
| 241 | + |
| 242 | + selectedOpts.ajax.win = selectedOpts.ajax.success; |
| 243 | + |
| 244 | + ajaxLoader = $.ajax($.extend({}, selectedOpts.ajax, { |
| 245 | + url : href, |
| 246 | + data : selectedOpts.ajax.data || {}, |
| 247 | + error : function(XMLHttpRequest, textStatus, errorThrown) { |
| 248 | + if ( XMLHttpRequest.status > 0 ) { |
| 249 | + _error(); |
| 250 | + } |
| 251 | + }, |
| 252 | + success : function(data, textStatus, XMLHttpRequest) { |
| 253 | + var o = typeof XMLHttpRequest == 'object' ? XMLHttpRequest : ajaxLoader; |
| 254 | + if (o.status == 200) { |
| 255 | + if ( typeof selectedOpts.ajax.win == 'function' ) { |
| 256 | + ret = selectedOpts.ajax.win(href, data, textStatus, XMLHttpRequest); |
| 257 | + |
| 258 | + if (ret === false) { |
| 259 | + loading.hide(); |
| 260 | + return; |
| 261 | + } else if (typeof ret == 'string' || typeof ret == 'object') { |
| 262 | + data = ret; |
| 263 | + } |
| 264 | + } |
| 265 | + |
| 266 | + tmp.html( data ); |
| 267 | + _process_inline(); |
| 268 | + } |
| 269 | + } |
| 270 | + })); |
| 271 | + |
| 272 | + break; |
| 273 | + |
| 274 | + case 'iframe': |
| 275 | + _show(); |
| 276 | + break; |
| 277 | + } |
| 278 | + }, |
| 279 | + |
| 280 | + _process_inline = function() { |
| 281 | + var |
| 282 | + w = selectedOpts.width, |
| 283 | + h = selectedOpts.height; |
| 284 | + |
| 285 | + if (w.toString().indexOf('%') > -1) { |
| 286 | + w = parseInt( ($(window).width() - (selectedOpts.margin * 2)) * parseFloat(w) / 100, 10) + 'px'; |
| 287 | + |
| 288 | + } else { |
| 289 | + w = w == 'auto' ? 'auto' : w + 'px'; |
| 290 | + } |
| 291 | + |
| 292 | + if (h.toString().indexOf('%') > -1) { |
| 293 | + h = parseInt( ($(window).height() - (selectedOpts.margin * 2)) * parseFloat(h) / 100, 10) + 'px'; |
| 294 | + |
| 295 | + } else { |
| 296 | + h = h == 'auto' ? 'auto' : h + 'px'; |
| 297 | + } |
| 298 | + |
| 299 | + tmp.wrapInner('<div style="width:' + w + ';height:' + h + ';overflow: ' + (selectedOpts.scrolling == 'auto' ? 'auto' : (selectedOpts.scrolling == 'yes' ? 'scroll' : 'hidden')) + ';position:relative;"></div>'); |
| 300 | + |
| 301 | + selectedOpts.width = tmp.width(); |
| 302 | + selectedOpts.height = tmp.height(); |
| 303 | + |
| 304 | + _show(); |
| 305 | + }, |
| 306 | + |
| 307 | + _process_image = function() { |
| 308 | + selectedOpts.width = imgPreloader.width; |
| 309 | + selectedOpts.height = imgPreloader.height; |
| 310 | + |
| 311 | + $("<img />").attr({ |
| 312 | + 'id' : 'fancybox-img', |
| 313 | + 'src' : imgPreloader.src, |
| 314 | + 'alt' : selectedOpts.title |
| 315 | + }).appendTo( tmp ); |
| 316 | + |
| 317 | + _show(); |
| 318 | + }, |
| 319 | + |
| 320 | + _show = function() { |
| 321 | + var pos, equal; |
| 322 | + |
| 323 | + loading.hide(); |
| 324 | + |
| 325 | + if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { |
| 326 | + $.event.trigger('fancybox-cancel'); |
| 327 | + |
| 328 | + busy = false; |
| 329 | + return; |
| 330 | + } |
| 331 | + |
| 332 | + busy = true; |
| 333 | + |
| 334 | + $(content.add( overlay )).unbind(); |
| 335 | + |
| 336 | + $(window).unbind("resize.fb scroll.fb"); |
| 337 | + $(document).unbind('keydown.fb'); |
| 338 | + |
| 339 | + if (wrap.is(":visible") && currentOpts.titlePosition !== 'outside') { |
| 340 | + wrap.css('height', wrap.height()); |
| 341 | + } |
| 342 | + |
| 343 | + currentArray = selectedArray; |
| 344 | + currentIndex = selectedIndex; |
| 345 | + currentOpts = selectedOpts; |
| 346 | + |
| 347 | + if (currentOpts.overlayShow) { |
| 348 | + overlay.css({ |
| 349 | + 'background-color' : currentOpts.overlayColor, |
| 350 | + 'opacity' : currentOpts.overlayOpacity, |
| 351 | + 'cursor' : currentOpts.hideOnOverlayClick ? 'pointer' : 'auto', |
| 352 | + 'height' : $(document).height() |
| 353 | + }); |
| 354 | + |
| 355 | + if (!overlay.is(':visible')) { |
| 356 | + if (isIE6) { |
| 357 | + $('select:not(#fancybox-tmp select)').filter(function() { |
| 358 | + return this.style.visibility !== 'hidden'; |
| 359 | + }).css({'visibility' : 'hidden'}).one('fancybox-cleanup', function() { |
| 360 | + this.style.visibility = 'inherit'; |
| 361 | + }); |
| 362 | + } |
| 363 | + |
| 364 | + overlay.show(); |
| 365 | + } |
| 366 | + } else { |
| 367 | + overlay.hide(); |
| 368 | + } |
| 369 | + |
| 370 | + final_pos = _get_zoom_to(); |
| 371 | + |
| 372 | + _process_title(); |
| 373 | + |
| 374 | + if (wrap.is(":visible")) { |
| 375 | + $( close.add( nav_left ).add( nav_right ) ).hide(); |
| 376 | + |
| 377 | + pos = wrap.position(), |
| 378 | + |
| 379 | + start_pos = { |
| 380 | + top : pos.top, |
| 381 | + left : pos.left, |
| 382 | + width : wrap.width(), |
| 383 | + height : wrap.height() |
| 384 | + }; |
| 385 | + |
| 386 | + equal = (start_pos.width == final_pos.width && start_pos.height == final_pos.height); |
| 387 | + |
| 388 | + content.fadeTo(currentOpts.changeFade, 0.3, function() { |
| 389 | + var finish_resizing = function() { |
| 390 | + content.html( tmp.contents() ).fadeTo(currentOpts.changeFade, 1, _finish); |
| 391 | + }; |
| 392 | + |
| 393 | + $.event.trigger('fancybox-change'); |
| 394 | + |
| 395 | + content |
| 396 | + .empty() |
| 397 | + .removeAttr('filter') |
| 398 | + .css({ |
| 399 | + 'border-width' : currentOpts.padding, |
| 400 | + 'width' : final_pos.width - currentOpts.padding * 2, |
| 401 | + 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 |
| 402 | + }); |
| 403 | + |
| 404 | + if (equal) { |
| 405 | + finish_resizing(); |
| 406 | + |
| 407 | + } else { |
| 408 | + fx.prop = 0; |
| 409 | + |
| 410 | + $(fx).animate({prop: 1}, { |
| 411 | + duration : currentOpts.changeSpeed, |
| 412 | + easing : currentOpts.easingChange, |
| 413 | + step : _draw, |
| 414 | + complete : finish_resizing |
| 415 | + }); |
| 416 | + } |
| 417 | + }); |
| 418 | + |
| 419 | + return; |
| 420 | + } |
| 421 | + |
| 422 | + wrap.removeAttr("style"); |
| 423 | + |
| 424 | + content.css('border-width', currentOpts.padding); |
| 425 | + |
| 426 | + if (currentOpts.transitionIn == 'elastic') { |
| 427 | + start_pos = _get_zoom_from(); |
| 428 | + |
| 429 | + content.html( tmp.contents() ); |
| 430 | + |
| 431 | + wrap.show(); |
| 432 | + |
| 433 | + if (currentOpts.opacity) { |
| 434 | + final_pos.opacity = 0; |
| 435 | + } |
| 436 | + |
| 437 | + fx.prop = 0; |
| 438 | + |
| 439 | + $(fx).animate({prop: 1}, { |
| 440 | + duration : currentOpts.speedIn, |
| 441 | + easing : currentOpts.easingIn, |
| 442 | + step : _draw, |
| 443 | + complete : _finish |
| 444 | + }); |
| 445 | + |
| 446 | + return; |
| 447 | + } |
| 448 | + |
| 449 | + if (currentOpts.titlePosition == 'inside' && titleHeight > 0) { |
| 450 | + title.show(); |
| 451 | + } |
| 452 | + |
| 453 | + content |
| 454 | + .css({ |
| 455 | + 'width' : final_pos.width - currentOpts.padding * 2, |
| 456 | + 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 |
| 457 | + }) |
| 458 | + .html( tmp.contents() ); |
| 459 | + |
| 460 | + wrap |
| 461 | + .css(final_pos) |
| 462 | + .fadeIn( currentOpts.transitionIn == 'none' ? 0 : currentOpts.speedIn, _finish ); |
| 463 | + }, |
| 464 | + |
| 465 | + _format_title = function(title) { |
| 466 | + if (title && title.length) { |
| 467 | + if (currentOpts.titlePosition == 'float') { |
| 468 | + return '<table id="fancybox-title-float-wrap" cellpadding="0" cellspacing="0"><tr><td id="fancybox-title-float-left"></td><td id="fancybox-title-float-main">' + title + '</td><td id="fancybox-title-float-right"></td></tr></table>'; |
| 469 | + } |
| 470 | + |
| 471 | + return '<div id="fancybox-title-' + currentOpts.titlePosition + '">' + title + '</div>'; |
| 472 | + } |
| 473 | + |
| 474 | + return false; |
| 475 | + }, |
| 476 | + |
| 477 | + _process_title = function() { |
| 478 | + titleStr = currentOpts.title || ''; |
| 479 | + titleHeight = 0; |
| 480 | + |
| 481 | + title |
| 482 | + .empty() |
| 483 | + .removeAttr('style') |
| 484 | + .removeClass(); |
| 485 | + |
| 486 | + if (currentOpts.titleShow === false) { |
| 487 | + title.hide(); |
| 488 | + return; |
| 489 | + } |
| 490 | + |
| 491 | + titleStr = $.isFunction(currentOpts.titleFormat) ? currentOpts.titleFormat(titleStr, currentArray, currentIndex, currentOpts) : _format_title(titleStr); |
| 492 | + |
| 493 | + if (!titleStr || titleStr === '') { |
| 494 | + title.hide(); |
| 495 | + return; |
| 496 | + } |
| 497 | + |
| 498 | + title |
| 499 | + .addClass('fancybox-title-' + currentOpts.titlePosition) |
| 500 | + .html( titleStr ) |
| 501 | + .appendTo( 'body' ) |
| 502 | + .show(); |
| 503 | + |
| 504 | + switch (currentOpts.titlePosition) { |
| 505 | + case 'inside': |
| 506 | + title |
| 507 | + .css({ |
| 508 | + 'width' : final_pos.width - (currentOpts.padding * 2), |
| 509 | + 'marginLeft' : currentOpts.padding, |
| 510 | + 'marginRight' : currentOpts.padding |
| 511 | + }); |
| 512 | + |
| 513 | + titleHeight = title.outerHeight(true); |
| 514 | + |
| 515 | + title.appendTo( outer ); |
| 516 | + |
| 517 | + final_pos.height += titleHeight; |
| 518 | + break; |
| 519 | + |
| 520 | + case 'over': |
| 521 | + title |
| 522 | + .css({ |
| 523 | + 'marginLeft' : currentOpts.padding, |
| 524 | + 'width' : final_pos.width - (currentOpts.padding * 2), |
| 525 | + 'bottom' : currentOpts.padding |
| 526 | + }) |
| 527 | + .appendTo( outer ); |
| 528 | + break; |
| 529 | + |
| 530 | + case 'float': |
| 531 | + title |
| 532 | + .css('left', parseInt((title.width() - final_pos.width - 40)/ 2, 10) * -1) |
| 533 | + .appendTo( wrap ); |
| 534 | + break; |
| 535 | + |
| 536 | + default: |
| 537 | + title |
| 538 | + .css({ |
| 539 | + 'width' : final_pos.width - (currentOpts.padding * 2), |
| 540 | + 'paddingLeft' : currentOpts.padding, |
| 541 | + 'paddingRight' : currentOpts.padding |
| 542 | + }) |
| 543 | + .appendTo( wrap ); |
| 544 | + break; |
| 545 | + } |
| 546 | + |
| 547 | + title.hide(); |
| 548 | + }, |
| 549 | + |
| 550 | + _set_navigation = function() { |
| 551 | + if (currentOpts.enableEscapeButton || currentOpts.enableKeyboardNav) { |
| 552 | + $(document).bind('keydown.fb', function(e) { |
| 553 | + if (e.keyCode == 27 && currentOpts.enableEscapeButton) { |
| 554 | + e.preventDefault(); |
| 555 | + $.fancybox.close(); |
| 556 | + |
| 557 | + } else if ((e.keyCode == 37 || e.keyCode == 39) && currentOpts.enableKeyboardNav && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') { |
| 558 | + e.preventDefault(); |
| 559 | + $.fancybox[ e.keyCode == 37 ? 'prev' : 'next'](); |
| 560 | + } |
| 561 | + }); |
| 562 | + } |
| 563 | + |
| 564 | + if (!currentOpts.showNavArrows) { |
| 565 | + nav_left.hide(); |
| 566 | + nav_right.hide(); |
| 567 | + return; |
| 568 | + } |
| 569 | + |
| 570 | + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex !== 0) { |
| 571 | + nav_left.show(); |
| 572 | + } |
| 573 | + |
| 574 | + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex != (currentArray.length -1)) { |
| 575 | + nav_right.show(); |
| 576 | + } |
| 577 | + }, |
| 578 | + |
| 579 | + _finish = function () { |
| 580 | + if (!$.support.opacity) { |
| 581 | + content.get(0).style.removeAttribute('filter'); |
| 582 | + wrap.get(0).style.removeAttribute('filter'); |
| 583 | + } |
| 584 | + |
| 585 | + if (selectedOpts.autoDimensions) { |
| 586 | + content.css('height', 'auto'); |
| 587 | + } |
| 588 | + |
| 589 | + wrap.css('height', 'auto'); |
| 590 | + |
| 591 | + if (titleStr && titleStr.length) { |
| 592 | + title.show(); |
| 593 | + } |
| 594 | + |
| 595 | + if (currentOpts.showCloseButton) { |
| 596 | + close.show(); |
| 597 | + } |
| 598 | + |
| 599 | + _set_navigation(); |
| 600 | + |
| 601 | + if (currentOpts.hideOnContentClick) { |
| 602 | + content.bind('click', $.fancybox.close); |
| 603 | + } |
| 604 | + |
| 605 | + if (currentOpts.hideOnOverlayClick) { |
| 606 | + overlay.bind('click', $.fancybox.close); |
| 607 | + } |
| 608 | + |
| 609 | + $(window).bind("resize.fb", $.fancybox.resize); |
| 610 | + |
| 611 | + if (currentOpts.centerOnScroll) { |
| 612 | + $(window).bind("scroll.fb", $.fancybox.center); |
| 613 | + } |
| 614 | + |
| 615 | + if (currentOpts.type == 'iframe') { |
| 616 | + $('<iframe id="fancybox-frame" name="fancybox-frame' + new Date().getTime() + '" frameborder="0" hspace="0" ' + ($.browser.msie ? 'allowtransparency="true""' : '') + ' scrolling="' + selectedOpts.scrolling + '" src="' + currentOpts.href + '"></iframe>').appendTo(content); |
| 617 | + } |
| 618 | + |
| 619 | + wrap.show(); |
| 620 | + |
| 621 | + busy = false; |
| 622 | + |
| 623 | + $.fancybox.center(); |
| 624 | + |
| 625 | + currentOpts.onComplete(currentArray, currentIndex, currentOpts); |
| 626 | + |
| 627 | + _preload_images(); |
| 628 | + }, |
| 629 | + |
| 630 | + _preload_images = function() { |
| 631 | + var href, |
| 632 | + objNext; |
| 633 | + |
| 634 | + if ((currentArray.length -1) > currentIndex) { |
| 635 | + href = currentArray[ currentIndex + 1 ].href; |
| 636 | + |
| 637 | + if (typeof href !== 'undefined' && href.match(imgRegExp)) { |
| 638 | + objNext = new Image(); |
| 639 | + objNext.src = href; |
| 640 | + } |
| 641 | + } |
| 642 | + |
| 643 | + if (currentIndex > 0) { |
| 644 | + href = currentArray[ currentIndex - 1 ].href; |
| 645 | + |
| 646 | + if (typeof href !== 'undefined' && href.match(imgRegExp)) { |
| 647 | + objNext = new Image(); |
| 648 | + objNext.src = href; |
| 649 | + } |
| 650 | + } |
| 651 | + }, |
| 652 | + |
| 653 | + _draw = function(pos) { |
| 654 | + var dim = { |
| 655 | + width : parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10), |
| 656 | + height : parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10), |
| 657 | + |
| 658 | + top : parseInt(start_pos.top + (final_pos.top - start_pos.top) * pos, 10), |
| 659 | + left : parseInt(start_pos.left + (final_pos.left - start_pos.left) * pos, 10) |
| 660 | + }; |
| 661 | + |
| 662 | + if (typeof final_pos.opacity !== 'undefined') { |
| 663 | + dim.opacity = pos < 0.5 ? 0.5 : pos; |
| 664 | + } |
| 665 | + |
| 666 | + wrap.css(dim); |
| 667 | + |
| 668 | + content.css({ |
| 669 | + 'width' : dim.width - currentOpts.padding * 2, |
| 670 | + 'height' : dim.height - (titleHeight * pos) - currentOpts.padding * 2 |
| 671 | + }); |
| 672 | + }, |
| 673 | + |
| 674 | + _get_viewport = function() { |
| 675 | + return [ |
| 676 | + $(window).width() - (currentOpts.margin * 2), |
| 677 | + $(window).height() - (currentOpts.margin * 2), |
| 678 | + $(document).scrollLeft() + currentOpts.margin, |
| 679 | + $(document).scrollTop() + currentOpts.margin |
| 680 | + ]; |
| 681 | + }, |
| 682 | + |
| 683 | + _get_zoom_to = function () { |
| 684 | + var view = _get_viewport(), |
| 685 | + to = {}, |
| 686 | + resize = currentOpts.autoScale, |
| 687 | + double_padding = currentOpts.padding * 2, |
| 688 | + ratio; |
| 689 | + |
| 690 | + if (currentOpts.width.toString().indexOf('%') > -1) { |
| 691 | + to.width = parseInt((view[0] * parseFloat(currentOpts.width)) / 100, 10); |
| 692 | + } else { |
| 693 | + to.width = currentOpts.width + double_padding; |
| 694 | + } |
| 695 | + |
| 696 | + if (currentOpts.height.toString().indexOf('%') > -1) { |
| 697 | + to.height = parseInt((view[1] * parseFloat(currentOpts.height)) / 100, 10); |
| 698 | + } else { |
| 699 | + to.height = currentOpts.height + double_padding; |
| 700 | + } |
| 701 | + |
| 702 | + if (resize && (to.width > view[0] || to.height > view[1])) { |
| 703 | + if (selectedOpts.type == 'image' || selectedOpts.type == 'swf') { |
| 704 | + ratio = (currentOpts.width ) / (currentOpts.height ); |
| 705 | + |
| 706 | + if ((to.width ) > view[0]) { |
| 707 | + to.width = view[0]; |
| 708 | + to.height = parseInt(((to.width - double_padding) / ratio) + double_padding, 10); |
| 709 | + } |
| 710 | + |
| 711 | + if ((to.height) > view[1]) { |
| 712 | + to.height = view[1]; |
| 713 | + to.width = parseInt(((to.height - double_padding) * ratio) + double_padding, 10); |
| 714 | + } |
| 715 | + |
| 716 | + } else { |
| 717 | + to.width = Math.min(to.width, view[0]); |
| 718 | + to.height = Math.min(to.height, view[1]); |
| 719 | + } |
| 720 | + } |
| 721 | + |
| 722 | + to.top = parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - to.height - 40) * 0.5)), 10); |
| 723 | + to.left = parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - to.width - 40) * 0.5)), 10); |
| 724 | + |
| 725 | + return to; |
| 726 | + }, |
| 727 | + |
| 728 | + _get_obj_pos = function(obj) { |
| 729 | + var pos = obj.offset(); |
| 730 | + |
| 731 | + pos.top += parseInt( obj.css('paddingTop'), 10 ) || 0; |
| 732 | + pos.left += parseInt( obj.css('paddingLeft'), 10 ) || 0; |
| 733 | + |
| 734 | + pos.top += parseInt( obj.css('border-top-width'), 10 ) || 0; |
| 735 | + pos.left += parseInt( obj.css('border-left-width'), 10 ) || 0; |
| 736 | + |
| 737 | + pos.width = obj.width(); |
| 738 | + pos.height = obj.height(); |
| 739 | + |
| 740 | + return pos; |
| 741 | + }, |
| 742 | + |
| 743 | + _get_zoom_from = function() { |
| 744 | + var orig = selectedOpts.orig ? $(selectedOpts.orig) : false, |
| 745 | + from = {}, |
| 746 | + pos, |
| 747 | + view; |
| 748 | + |
| 749 | + if (orig && orig.length) { |
| 750 | + pos = _get_obj_pos(orig); |
| 751 | + |
| 752 | + from = { |
| 753 | + width : pos.width + (currentOpts.padding * 2), |
| 754 | + height : pos.height + (currentOpts.padding * 2), |
| 755 | + top : pos.top - currentOpts.padding - 20, |
| 756 | + left : pos.left - currentOpts.padding - 20 |
| 757 | + }; |
| 758 | + |
| 759 | + } else { |
| 760 | + view = _get_viewport(); |
| 761 | + |
| 762 | + from = { |
| 763 | + width : currentOpts.padding * 2, |
| 764 | + height : currentOpts.padding * 2, |
| 765 | + top : parseInt(view[3] + view[1] * 0.5, 10), |
| 766 | + left : parseInt(view[2] + view[0] * 0.5, 10) |
| 767 | + }; |
| 768 | + } |
| 769 | + |
| 770 | + return from; |
| 771 | + }, |
| 772 | + |
| 773 | + _animate_loading = function() { |
| 774 | + if (!loading.is(':visible')){ |
| 775 | + clearInterval(loadingTimer); |
| 776 | + return; |
| 777 | + } |
| 778 | + |
| 779 | + $('div', loading).css('top', (loadingFrame * -40) + 'px'); |
| 780 | + |
| 781 | + loadingFrame = (loadingFrame + 1) % 12; |
| 782 | + }; |
| 783 | + |
| 784 | + /* |
| 785 | + * Public methods |
| 786 | + */ |
| 787 | + |
| 788 | + $.fn.fancybox = function(options) { |
| 789 | + if (!$(this).length) { |
| 790 | + return this; |
| 791 | + } |
| 792 | + |
| 793 | + $(this) |
| 794 | + .data('fancybox', $.extend({}, options, ($.metadata ? $(this).metadata() : {}))) |
| 795 | + .unbind('click.fb') |
| 796 | + .bind('click.fb', function(e) { |
| 797 | + e.preventDefault(); |
| 798 | + |
| 799 | + if (busy) { |
| 800 | + return; |
| 801 | + } |
| 802 | + |
| 803 | + busy = true; |
| 804 | + |
| 805 | + $(this).blur(); |
| 806 | + |
| 807 | + selectedArray = []; |
| 808 | + selectedIndex = 0; |
| 809 | + |
| 810 | + var rel = $(this).attr('rel') || ''; |
| 811 | + |
| 812 | + if (!rel || rel == '' || rel === 'nofollow') { |
| 813 | + selectedArray.push(this); |
| 814 | + |
| 815 | + } else { |
| 816 | + selectedArray = $("a[rel=" + rel + "], area[rel=" + rel + "]"); |
| 817 | + selectedIndex = selectedArray.index( this ); |
| 818 | + } |
| 819 | + |
| 820 | + _start(); |
| 821 | + |
| 822 | + return; |
| 823 | + }); |
| 824 | + |
| 825 | + return this; |
| 826 | + }; |
| 827 | + |
| 828 | + $.fancybox = function(obj) { |
| 829 | + var opts; |
| 830 | + |
| 831 | + if (busy) { |
| 832 | + return; |
| 833 | + } |
| 834 | + |
| 835 | + busy = true; |
| 836 | + opts = typeof arguments[1] !== 'undefined' ? arguments[1] : {}; |
| 837 | + |
| 838 | + selectedArray = []; |
| 839 | + selectedIndex = parseInt(opts.index, 10) || 0; |
| 840 | + |
| 841 | + if ($.isArray(obj)) { |
| 842 | + for (var i = 0, j = obj.length; i < j; i++) { |
| 843 | + if (typeof obj[i] == 'object') { |
| 844 | + $(obj[i]).data('fancybox', $.extend({}, opts, obj[i])); |
| 845 | + } else { |
| 846 | + obj[i] = $({}).data('fancybox', $.extend({content : obj[i]}, opts)); |
| 847 | + } |
| 848 | + } |
| 849 | + |
| 850 | + selectedArray = jQuery.merge(selectedArray, obj); |
| 851 | + |
| 852 | + } else { |
| 853 | + if (typeof obj == 'object') { |
| 854 | + $(obj).data('fancybox', $.extend({}, opts, obj)); |
| 855 | + } else { |
| 856 | + obj = $({}).data('fancybox', $.extend({content : obj}, opts)); |
| 857 | + } |
| 858 | + |
| 859 | + selectedArray.push(obj); |
| 860 | + } |
| 861 | + |
| 862 | + if (selectedIndex > selectedArray.length || selectedIndex < 0) { |
| 863 | + selectedIndex = 0; |
| 864 | + } |
| 865 | + |
| 866 | + _start(); |
| 867 | + }; |
| 868 | + |
| 869 | + $.fancybox.showActivity = function() { |
| 870 | + clearInterval(loadingTimer); |
| 871 | + |
| 872 | + loading.show(); |
| 873 | + loadingTimer = setInterval(_animate_loading, 66); |
| 874 | + }; |
| 875 | + |
| 876 | + $.fancybox.hideActivity = function() { |
| 877 | + loading.hide(); |
| 878 | + }; |
| 879 | + |
| 880 | + $.fancybox.next = function() { |
| 881 | + return $.fancybox.pos( currentIndex + 1); |
| 882 | + }; |
| 883 | + |
| 884 | + $.fancybox.prev = function() { |
| 885 | + return $.fancybox.pos( currentIndex - 1); |
| 886 | + }; |
| 887 | + |
| 888 | + $.fancybox.pos = function(pos) { |
| 889 | + if (busy) { |
| 890 | + return; |
| 891 | + } |
| 892 | + |
| 893 | + pos = parseInt(pos); |
| 894 | + |
| 895 | + selectedArray = currentArray; |
| 896 | + |
| 897 | + if (pos > -1 && pos < currentArray.length) { |
| 898 | + selectedIndex = pos; |
| 899 | + _start(); |
| 900 | + |
| 901 | + } else if (currentOpts.cyclic && currentArray.length > 1) { |
| 902 | + selectedIndex = pos >= currentArray.length ? 0 : currentArray.length - 1; |
| 903 | + _start(); |
| 904 | + } |
| 905 | + |
| 906 | + return; |
| 907 | + }; |
| 908 | + |
| 909 | + $.fancybox.cancel = function() { |
| 910 | + if (busy) { |
| 911 | + return; |
| 912 | + } |
| 913 | + |
| 914 | + busy = true; |
| 915 | + |
| 916 | + $.event.trigger('fancybox-cancel'); |
| 917 | + |
| 918 | + _abort(); |
| 919 | + |
| 920 | + selectedOpts.onCancel(selectedArray, selectedIndex, selectedOpts); |
| 921 | + |
| 922 | + busy = false; |
| 923 | + }; |
| 924 | + |
| 925 | + // Note: within an iframe use - parent.$.fancybox.close(); |
| 926 | + $.fancybox.close = function() { |
| 927 | + if (busy || wrap.is(':hidden')) { |
| 928 | + return; |
| 929 | + } |
| 930 | + |
| 931 | + busy = true; |
| 932 | + |
| 933 | + if (currentOpts && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { |
| 934 | + busy = false; |
| 935 | + return; |
| 936 | + } |
| 937 | + |
| 938 | + _abort(); |
| 939 | + |
| 940 | + $(close.add( nav_left ).add( nav_right )).hide(); |
| 941 | + |
| 942 | + $(content.add( overlay )).unbind(); |
| 943 | + |
| 944 | + $(window).unbind("resize.fb scroll.fb"); |
| 945 | + $(document).unbind('keydown.fb'); |
| 946 | + |
| 947 | + content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank'); |
| 948 | + |
| 949 | + if (currentOpts.titlePosition !== 'inside') { |
| 950 | + title.empty(); |
| 951 | + } |
| 952 | + |
| 953 | + wrap.stop(); |
| 954 | + |
| 955 | + function _cleanup() { |
| 956 | + overlay.fadeOut('fast'); |
| 957 | + |
| 958 | + title.empty().hide(); |
| 959 | + wrap.hide(); |
| 960 | + |
| 961 | + $.event.trigger('fancybox-cleanup'); |
| 962 | + |
| 963 | + content.empty(); |
| 964 | + |
| 965 | + currentOpts.onClosed(currentArray, currentIndex, currentOpts); |
| 966 | + |
| 967 | + currentArray = selectedOpts = []; |
| 968 | + currentIndex = selectedIndex = 0; |
| 969 | + currentOpts = selectedOpts = {}; |
| 970 | + |
| 971 | + busy = false; |
| 972 | + } |
| 973 | + |
| 974 | + if (currentOpts.transitionOut == 'elastic') { |
| 975 | + start_pos = _get_zoom_from(); |
| 976 | + |
| 977 | + var pos = wrap.position(); |
| 978 | + |
| 979 | + final_pos = { |
| 980 | + top : pos.top , |
| 981 | + left : pos.left, |
| 982 | + width : wrap.width(), |
| 983 | + height : wrap.height() |
| 984 | + }; |
| 985 | + |
| 986 | + if (currentOpts.opacity) { |
| 987 | + final_pos.opacity = 1; |
| 988 | + } |
| 989 | + |
| 990 | + title.empty().hide(); |
| 991 | + |
| 992 | + fx.prop = 1; |
| 993 | + |
| 994 | + $(fx).animate({ prop: 0 }, { |
| 995 | + duration : currentOpts.speedOut, |
| 996 | + easing : currentOpts.easingOut, |
| 997 | + step : _draw, |
| 998 | + complete : _cleanup |
| 999 | + }); |
| 1000 | + |
| 1001 | + } else { |
| 1002 | + wrap.fadeOut( currentOpts.transitionOut == 'none' ? 0 : currentOpts.speedOut, _cleanup); |
| 1003 | + } |
| 1004 | + }; |
| 1005 | + |
| 1006 | + $.fancybox.resize = function() { |
| 1007 | + if (overlay.is(':visible')) { |
| 1008 | + overlay.css('height', $(document).height()); |
| 1009 | + } |
| 1010 | + |
| 1011 | + $.fancybox.center(true); |
| 1012 | + }; |
| 1013 | + |
| 1014 | + $.fancybox.center = function() { |
| 1015 | + var view, align; |
| 1016 | + |
| 1017 | + if (busy) { |
| 1018 | + return; |
| 1019 | + } |
| 1020 | + |
| 1021 | + align = arguments[0] === true ? 1 : 0; |
| 1022 | + view = _get_viewport(); |
| 1023 | + |
| 1024 | + if (!align && (wrap.width() > view[0] || wrap.height() > view[1])) { |
| 1025 | + return; |
| 1026 | + } |
| 1027 | + |
| 1028 | + wrap |
| 1029 | + .stop() |
| 1030 | + .animate({ |
| 1031 | + 'top' : parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - content.height() - 40) * 0.5) - currentOpts.padding)), |
| 1032 | + 'left' : parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - content.width() - 40) * 0.5) - currentOpts.padding)) |
| 1033 | + }, typeof arguments[0] == 'number' ? arguments[0] : 200); |
| 1034 | + }; |
| 1035 | + |
| 1036 | + $.fancybox.init = function() { |
| 1037 | + if ($("#fancybox-wrap").length) { |
| 1038 | + return; |
| 1039 | + } |
| 1040 | + |
| 1041 | + $('body').append( |
| 1042 | + tmp = $('<div id="fancybox-tmp"></div>'), |
| 1043 | + loading = $('<div id="fancybox-loading"><div></div></div>'), |
| 1044 | + overlay = $('<div id="fancybox-overlay"></div>'), |
| 1045 | + wrap = $('<div id="fancybox-wrap"></div>') |
| 1046 | + ); |
| 1047 | + |
| 1048 | + outer = $('<div id="fancybox-outer"></div>') |
| 1049 | + .append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>') |
| 1050 | + .appendTo( wrap ); |
| 1051 | + |
| 1052 | + outer.append( |
| 1053 | + content = $('<div id="fancybox-content"></div>'), |
| 1054 | + close = $('<a id="fancybox-close"></a>'), |
| 1055 | + title = $('<div id="fancybox-title"></div>'), |
| 1056 | + |
| 1057 | + nav_left = $('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'), |
| 1058 | + nav_right = $('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>') |
| 1059 | + ); |
| 1060 | + |
| 1061 | + close.click($.fancybox.close); |
| 1062 | + loading.click($.fancybox.cancel); |
| 1063 | + |
| 1064 | + nav_left.click(function(e) { |
| 1065 | + e.preventDefault(); |
| 1066 | + $.fancybox.prev(); |
| 1067 | + }); |
| 1068 | + |
| 1069 | + nav_right.click(function(e) { |
| 1070 | + e.preventDefault(); |
| 1071 | + $.fancybox.next(); |
| 1072 | + }); |
| 1073 | + |
| 1074 | + if ($.fn.mousewheel) { |
| 1075 | + wrap.bind('mousewheel.fb', function(e, delta) { |
| 1076 | + if (busy) { |
| 1077 | + e.preventDefault(); |
| 1078 | + |
| 1079 | + } else if ($(e.target).get(0).clientHeight == 0 || $(e.target).get(0).scrollHeight === $(e.target).get(0).clientHeight) { |
| 1080 | + e.preventDefault(); |
| 1081 | + $.fancybox[ delta > 0 ? 'prev' : 'next'](); |
| 1082 | + } |
| 1083 | + }); |
| 1084 | + } |
| 1085 | + |
| 1086 | + if (!$.support.opacity) { |
| 1087 | + wrap.addClass('fancybox-ie'); |
| 1088 | + } |
| 1089 | + |
| 1090 | + if (isIE6) { |
| 1091 | + loading.addClass('fancybox-ie6'); |
| 1092 | + wrap.addClass('fancybox-ie6'); |
| 1093 | + |
| 1094 | + $('<iframe id="fancybox-hide-sel-frame" src="' + (/^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank' ) + '" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(outer); |
| 1095 | + } |
| 1096 | + }; |
| 1097 | + |
| 1098 | + $.fn.fancybox.defaults = { |
| 1099 | + padding : 10, |
| 1100 | + margin : 40, |
| 1101 | + opacity : false, |
| 1102 | + modal : false, |
| 1103 | + cyclic : false, |
| 1104 | + scrolling : 'auto', // 'auto', 'yes' or 'no' |
| 1105 | + |
| 1106 | + width : 560, |
| 1107 | + height : 340, |
| 1108 | + |
| 1109 | + autoScale : true, |
| 1110 | + autoDimensions : true, |
| 1111 | + centerOnScroll : false, |
| 1112 | + |
| 1113 | + ajax : {}, |
| 1114 | + swf : { wmode: 'transparent' }, |
| 1115 | + |
| 1116 | + hideOnOverlayClick : true, |
| 1117 | + hideOnContentClick : false, |
| 1118 | + |
| 1119 | + overlayShow : true, |
| 1120 | + overlayOpacity : 0.7, |
| 1121 | + overlayColor : '#777', |
| 1122 | + |
| 1123 | + titleShow : true, |
| 1124 | + titlePosition : 'float', // 'float', 'outside', 'inside' or 'over' |
| 1125 | + titleFormat : null, |
| 1126 | + titleFromAlt : false, |
| 1127 | + |
| 1128 | + transitionIn : 'fade', // 'elastic', 'fade' or 'none' |
| 1129 | + transitionOut : 'fade', // 'elastic', 'fade' or 'none' |
| 1130 | + |
| 1131 | + speedIn : 300, |
| 1132 | + speedOut : 300, |
| 1133 | + |
| 1134 | + changeSpeed : 300, |
| 1135 | + changeFade : 'fast', |
| 1136 | + |
| 1137 | + easingIn : 'swing', |
| 1138 | + easingOut : 'swing', |
| 1139 | + |
| 1140 | + showCloseButton : true, |
| 1141 | + showNavArrows : true, |
| 1142 | + enableEscapeButton : true, |
| 1143 | + enableKeyboardNav : true, |
| 1144 | + |
| 1145 | + onStart : function(){}, |
| 1146 | + onCancel : function(){}, |
| 1147 | + onComplete : function(){}, |
| 1148 | + onCleanup : function(){}, |
| 1149 | + onClosed : function(){}, |
| 1150 | + onError : function(){} |
| 1151 | + }; |
| 1152 | + |
| 1153 | + $(document).ready(function() { |
| 1154 | + $.fancybox.init(); |
| 1155 | + }); |
| 1156 | + |
| 1157 | +})(jQuery); |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.fancybox-1.3.4.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 1158 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_n.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_n.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 1159 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_close.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_close.png |
___________________________________________________________________ |
Added: svn:mime-type |
3 | 1160 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_ne.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_ne.png |
___________________________________________________________________ |
Added: svn:mime-type |
4 | 1161 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_s.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_s.png |
___________________________________________________________________ |
Added: svn:mime-type |
5 | 1162 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.easing-1.3.pack.js |
— | — | @@ -0,0 +1,72 @@ |
| 2 | +/* |
| 3 | + * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ |
| 4 | + * |
| 5 | + * Uses the built in easing capabilities added In jQuery 1.1 |
| 6 | + * to offer multiple easing options |
| 7 | + * |
| 8 | + * TERMS OF USE - jQuery Easing |
| 9 | + * |
| 10 | + * Open source under the BSD License. |
| 11 | + * |
| 12 | + * Copyright © 2008 George McGinley Smith |
| 13 | + * All rights reserved. |
| 14 | + * |
| 15 | + * Redistribution and use in source and binary forms, with or without modification, |
| 16 | + * are permitted provided that the following conditions are met: |
| 17 | + * |
| 18 | + * Redistributions of source code must retain the above copyright notice, this list of |
| 19 | + * conditions and the following disclaimer. |
| 20 | + * Redistributions in binary form must reproduce the above copyright notice, this list |
| 21 | + * of conditions and the following disclaimer in the documentation and/or other materials |
| 22 | + * provided with the distribution. |
| 23 | + * |
| 24 | + * Neither the name of the author nor the names of contributors may be used to endorse |
| 25 | + * or promote products derived from this software without specific prior written permission. |
| 26 | + * |
| 27 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY |
| 28 | + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| 29 | + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| 30 | + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 31 | + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| 32 | + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| 33 | + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| 34 | + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
| 35 | + * OF THE POSSIBILITY OF SUCH DAMAGE. |
| 36 | + * |
| 37 | +*/ |
| 38 | + |
| 39 | +// t: current time, b: begInnIng value, c: change In value, d: duration |
| 40 | +eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('h.i[\'1a\']=h.i[\'z\'];h.O(h.i,{y:\'D\',z:9(x,t,b,c,d){6 h.i[h.i.y](x,t,b,c,d)},17:9(x,t,b,c,d){6 c*(t/=d)*t+b},D:9(x,t,b,c,d){6-c*(t/=d)*(t-2)+b},13:9(x,t,b,c,d){e((t/=d/2)<1)6 c/2*t*t+b;6-c/2*((--t)*(t-2)-1)+b},X:9(x,t,b,c,d){6 c*(t/=d)*t*t+b},U:9(x,t,b,c,d){6 c*((t=t/d-1)*t*t+1)+b},R:9(x,t,b,c,d){e((t/=d/2)<1)6 c/2*t*t*t+b;6 c/2*((t-=2)*t*t+2)+b},N:9(x,t,b,c,d){6 c*(t/=d)*t*t*t+b},M:9(x,t,b,c,d){6-c*((t=t/d-1)*t*t*t-1)+b},L:9(x,t,b,c,d){e((t/=d/2)<1)6 c/2*t*t*t*t+b;6-c/2*((t-=2)*t*t*t-2)+b},K:9(x,t,b,c,d){6 c*(t/=d)*t*t*t*t+b},J:9(x,t,b,c,d){6 c*((t=t/d-1)*t*t*t*t+1)+b},I:9(x,t,b,c,d){e((t/=d/2)<1)6 c/2*t*t*t*t*t+b;6 c/2*((t-=2)*t*t*t*t+2)+b},G:9(x,t,b,c,d){6-c*8.C(t/d*(8.g/2))+c+b},15:9(x,t,b,c,d){6 c*8.n(t/d*(8.g/2))+b},12:9(x,t,b,c,d){6-c/2*(8.C(8.g*t/d)-1)+b},Z:9(x,t,b,c,d){6(t==0)?b:c*8.j(2,10*(t/d-1))+b},Y:9(x,t,b,c,d){6(t==d)?b+c:c*(-8.j(2,-10*t/d)+1)+b},W:9(x,t,b,c,d){e(t==0)6 b;e(t==d)6 b+c;e((t/=d/2)<1)6 c/2*8.j(2,10*(t-1))+b;6 c/2*(-8.j(2,-10*--t)+2)+b},V:9(x,t,b,c,d){6-c*(8.o(1-(t/=d)*t)-1)+b},S:9(x,t,b,c,d){6 c*8.o(1-(t=t/d-1)*t)+b},Q:9(x,t,b,c,d){e((t/=d/2)<1)6-c/2*(8.o(1-t*t)-1)+b;6 c/2*(8.o(1-(t-=2)*t)+1)+b},P:9(x,t,b,c,d){f s=1.l;f p=0;f a=c;e(t==0)6 b;e((t/=d)==1)6 b+c;e(!p)p=d*.3;e(a<8.w(c)){a=c;f s=p/4}m f s=p/(2*8.g)*8.r(c/a);6-(a*8.j(2,10*(t-=1))*8.n((t*d-s)*(2*8.g)/p))+b},H:9(x,t,b,c,d){f s=1.l;f p=0;f a=c;e(t==0)6 b;e((t/=d)==1)6 b+c;e(!p)p=d*.3;e(a<8.w(c)){a=c;f s=p/4}m f s=p/(2*8.g)*8.r(c/a);6 a*8.j(2,-10*t)*8.n((t*d-s)*(2*8.g)/p)+c+b},T:9(x,t,b,c,d){f s=1.l;f p=0;f a=c;e(t==0)6 b;e((t/=d/2)==2)6 b+c;e(!p)p=d*(.3*1.5);e(a<8.w(c)){a=c;f s=p/4}m f s=p/(2*8.g)*8.r(c/a);e(t<1)6-.5*(a*8.j(2,10*(t-=1))*8.n((t*d-s)*(2*8.g)/p))+b;6 a*8.j(2,-10*(t-=1))*8.n((t*d-s)*(2*8.g)/p)*.5+c+b},F:9(x,t,b,c,d,s){e(s==u)s=1.l;6 c*(t/=d)*t*((s+1)*t-s)+b},E:9(x,t,b,c,d,s){e(s==u)s=1.l;6 c*((t=t/d-1)*t*((s+1)*t+s)+1)+b},16:9(x,t,b,c,d,s){e(s==u)s=1.l;e((t/=d/2)<1)6 c/2*(t*t*(((s*=(1.B))+1)*t-s))+b;6 c/2*((t-=2)*t*(((s*=(1.B))+1)*t+s)+2)+b},A:9(x,t,b,c,d){6 c-h.i.v(x,d-t,0,c,d)+b},v:9(x,t,b,c,d){e((t/=d)<(1/2.k)){6 c*(7.q*t*t)+b}m e(t<(2/2.k)){6 c*(7.q*(t-=(1.5/2.k))*t+.k)+b}m e(t<(2.5/2.k)){6 c*(7.q*(t-=(2.14/2.k))*t+.11)+b}m{6 c*(7.q*(t-=(2.18/2.k))*t+.19)+b}},1b:9(x,t,b,c,d){e(t<d/2)6 h.i.A(x,t*2,0,c,d)*.5+b;6 h.i.v(x,t*2-d,0,c,d)*.5+c*.5+b}});',62,74,'||||||return||Math|function|||||if|var|PI|jQuery|easing|pow|75|70158|else|sin|sqrt||5625|asin|||undefined|easeOutBounce|abs||def|swing|easeInBounce|525|cos|easeOutQuad|easeOutBack|easeInBack|easeInSine|easeOutElastic|easeInOutQuint|easeOutQuint|easeInQuint|easeInOutQuart|easeOutQuart|easeInQuart|extend|easeInElastic|easeInOutCirc|easeInOutCubic|easeOutCirc|easeInOutElastic|easeOutCubic|easeInCirc|easeInOutExpo|easeInCubic|easeOutExpo|easeInExpo||9375|easeInOutSine|easeInOutQuad|25|easeOutSine|easeInOutBack|easeInQuad|625|984375|jswing|easeInOutBounce'.split('|'),0,{})) |
| 41 | + |
| 42 | +/* |
| 43 | + * |
| 44 | + * TERMS OF USE - EASING EQUATIONS |
| 45 | + * |
| 46 | + * Open source under the BSD License. |
| 47 | + * |
| 48 | + * Copyright © 2001 Robert Penner |
| 49 | + * All rights reserved. |
| 50 | + * |
| 51 | + * Redistribution and use in source and binary forms, with or without modification, |
| 52 | + * are permitted provided that the following conditions are met: |
| 53 | + * |
| 54 | + * Redistributions of source code must retain the above copyright notice, this list of |
| 55 | + * conditions and the following disclaimer. |
| 56 | + * Redistributions in binary form must reproduce the above copyright notice, this list |
| 57 | + * of conditions and the following disclaimer in the documentation and/or other materials |
| 58 | + * provided with the distribution. |
| 59 | + * |
| 60 | + * Neither the name of the author nor the names of contributors may be used to endorse |
| 61 | + * or promote products derived from this software without specific prior written permission. |
| 62 | + * |
| 63 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY |
| 64 | + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| 65 | + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| 66 | + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 67 | + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| 68 | + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| 69 | + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| 70 | + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
| 71 | + * OF THE POSSIBILITY OF SUCH DAMAGE. |
| 72 | + * |
| 73 | + */ |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.easing-1.3.pack.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 74 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_w.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_w.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 75 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.fancybox-1.3.4.css |
— | — | @@ -0,0 +1,359 @@ |
| 2 | +/* |
| 3 | + * FancyBox - jQuery Plugin |
| 4 | + * Simple and fancy lightbox alternative |
| 5 | + * |
| 6 | + * Examples and documentation at: http://fancybox.net |
| 7 | + * |
| 8 | + * Copyright (c) 2008 - 2010 Janis Skarnelis |
| 9 | + * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. |
| 10 | + * |
| 11 | + * Version: 1.3.4 (11/11/2010) |
| 12 | + * Requires: jQuery v1.3+ |
| 13 | + * |
| 14 | + * Dual licensed under the MIT and GPL licenses: |
| 15 | + * http://www.opensource.org/licenses/mit-license.php |
| 16 | + * http://www.gnu.org/licenses/gpl.html |
| 17 | + */ |
| 18 | + |
| 19 | +#fancybox-loading { |
| 20 | + position: fixed; |
| 21 | + top: 50%; |
| 22 | + left: 50%; |
| 23 | + width: 40px; |
| 24 | + height: 40px; |
| 25 | + margin-top: -20px; |
| 26 | + margin-left: -20px; |
| 27 | + cursor: pointer; |
| 28 | + overflow: hidden; |
| 29 | + z-index: 1104; |
| 30 | + display: none; |
| 31 | +} |
| 32 | + |
| 33 | +#fancybox-loading div { |
| 34 | + position: absolute; |
| 35 | + top: 0; |
| 36 | + left: 0; |
| 37 | + width: 40px; |
| 38 | + height: 480px; |
| 39 | + background-image: url('fancybox.png'); |
| 40 | +} |
| 41 | + |
| 42 | +#fancybox-overlay { |
| 43 | + position: absolute; |
| 44 | + top: 0; |
| 45 | + left: 0; |
| 46 | + width: 100%; |
| 47 | + z-index: 1100; |
| 48 | + display: none; |
| 49 | +} |
| 50 | + |
| 51 | +#fancybox-tmp { |
| 52 | + padding: 0; |
| 53 | + margin: 0; |
| 54 | + border: 0; |
| 55 | + overflow: auto; |
| 56 | + display: none; |
| 57 | +} |
| 58 | + |
| 59 | +#fancybox-wrap { |
| 60 | + position: absolute; |
| 61 | + top: 0; |
| 62 | + left: 0; |
| 63 | + padding: 20px; |
| 64 | + z-index: 1101; |
| 65 | + outline: none; |
| 66 | + display: none; |
| 67 | +} |
| 68 | + |
| 69 | +#fancybox-outer { |
| 70 | + position: relative; |
| 71 | + width: 100%; |
| 72 | + height: 100%; |
| 73 | + background: #fff; |
| 74 | +} |
| 75 | + |
| 76 | +#fancybox-content { |
| 77 | + width: 0; |
| 78 | + height: 0; |
| 79 | + padding: 0; |
| 80 | + outline: none; |
| 81 | + position: relative; |
| 82 | + overflow: hidden; |
| 83 | + z-index: 1102; |
| 84 | + border: 0px solid #fff; |
| 85 | +} |
| 86 | + |
| 87 | +#fancybox-hide-sel-frame { |
| 88 | + position: absolute; |
| 89 | + top: 0; |
| 90 | + left: 0; |
| 91 | + width: 100%; |
| 92 | + height: 100%; |
| 93 | + background: transparent; |
| 94 | + z-index: 1101; |
| 95 | +} |
| 96 | + |
| 97 | +#fancybox-close { |
| 98 | + position: absolute; |
| 99 | + top: -15px; |
| 100 | + right: -15px; |
| 101 | + width: 30px; |
| 102 | + height: 30px; |
| 103 | + background: transparent url('fancybox.png') -40px 0px; |
| 104 | + cursor: pointer; |
| 105 | + z-index: 1103; |
| 106 | + display: none; |
| 107 | +} |
| 108 | + |
| 109 | +#fancybox-error { |
| 110 | + color: #444; |
| 111 | + font: normal 12px/20px Arial; |
| 112 | + padding: 14px; |
| 113 | + margin: 0; |
| 114 | +} |
| 115 | + |
| 116 | +#fancybox-img { |
| 117 | + width: 100%; |
| 118 | + height: 100%; |
| 119 | + padding: 0; |
| 120 | + margin: 0; |
| 121 | + border: none; |
| 122 | + outline: none; |
| 123 | + line-height: 0; |
| 124 | + vertical-align: top; |
| 125 | +} |
| 126 | + |
| 127 | +#fancybox-frame { |
| 128 | + width: 100%; |
| 129 | + height: 100%; |
| 130 | + border: none; |
| 131 | + display: block; |
| 132 | +} |
| 133 | + |
| 134 | +#fancybox-left, #fancybox-right { |
| 135 | + position: absolute; |
| 136 | + bottom: 0px; |
| 137 | + height: 100%; |
| 138 | + width: 35%; |
| 139 | + cursor: pointer; |
| 140 | + outline: none; |
| 141 | + background: transparent url('blank.gif'); |
| 142 | + z-index: 1102; |
| 143 | + display: none; |
| 144 | +} |
| 145 | + |
| 146 | +#fancybox-left { |
| 147 | + left: 0px; |
| 148 | +} |
| 149 | + |
| 150 | +#fancybox-right { |
| 151 | + right: 0px; |
| 152 | +} |
| 153 | + |
| 154 | +#fancybox-left-ico, #fancybox-right-ico { |
| 155 | + position: absolute; |
| 156 | + top: 50%; |
| 157 | + left: -9999px; |
| 158 | + width: 30px; |
| 159 | + height: 30px; |
| 160 | + margin-top: -15px; |
| 161 | + cursor: pointer; |
| 162 | + z-index: 1102; |
| 163 | + display: block; |
| 164 | +} |
| 165 | + |
| 166 | +#fancybox-left-ico { |
| 167 | + background-image: url('fancybox.png'); |
| 168 | + background-position: -40px -30px; |
| 169 | +} |
| 170 | + |
| 171 | +#fancybox-right-ico { |
| 172 | + background-image: url('fancybox.png'); |
| 173 | + background-position: -40px -60px; |
| 174 | +} |
| 175 | + |
| 176 | +#fancybox-left:hover, #fancybox-right:hover { |
| 177 | + visibility: visible; /* IE6 */ |
| 178 | +} |
| 179 | + |
| 180 | +#fancybox-left:hover span { |
| 181 | + left: 20px; |
| 182 | +} |
| 183 | + |
| 184 | +#fancybox-right:hover span { |
| 185 | + left: auto; |
| 186 | + right: 20px; |
| 187 | +} |
| 188 | + |
| 189 | +.fancybox-bg { |
| 190 | + position: absolute; |
| 191 | + padding: 0; |
| 192 | + margin: 0; |
| 193 | + border: 0; |
| 194 | + width: 20px; |
| 195 | + height: 20px; |
| 196 | + z-index: 1001; |
| 197 | +} |
| 198 | + |
| 199 | +#fancybox-bg-n { |
| 200 | + top: -20px; |
| 201 | + left: 0; |
| 202 | + width: 100%; |
| 203 | + background-image: url('fancybox-x.png'); |
| 204 | +} |
| 205 | + |
| 206 | +#fancybox-bg-ne { |
| 207 | + top: -20px; |
| 208 | + right: -20px; |
| 209 | + background-image: url('fancybox.png'); |
| 210 | + background-position: -40px -162px; |
| 211 | +} |
| 212 | + |
| 213 | +#fancybox-bg-e { |
| 214 | + top: 0; |
| 215 | + right: -20px; |
| 216 | + height: 100%; |
| 217 | + background-image: url('fancybox-y.png'); |
| 218 | + background-position: -20px 0px; |
| 219 | +} |
| 220 | + |
| 221 | +#fancybox-bg-se { |
| 222 | + bottom: -20px; |
| 223 | + right: -20px; |
| 224 | + background-image: url('fancybox.png'); |
| 225 | + background-position: -40px -182px; |
| 226 | +} |
| 227 | + |
| 228 | +#fancybox-bg-s { |
| 229 | + bottom: -20px; |
| 230 | + left: 0; |
| 231 | + width: 100%; |
| 232 | + background-image: url('fancybox-x.png'); |
| 233 | + background-position: 0px -20px; |
| 234 | +} |
| 235 | + |
| 236 | +#fancybox-bg-sw { |
| 237 | + bottom: -20px; |
| 238 | + left: -20px; |
| 239 | + background-image: url('fancybox.png'); |
| 240 | + background-position: -40px -142px; |
| 241 | +} |
| 242 | + |
| 243 | +#fancybox-bg-w { |
| 244 | + top: 0; |
| 245 | + left: -20px; |
| 246 | + height: 100%; |
| 247 | + background-image: url('fancybox-y.png'); |
| 248 | +} |
| 249 | + |
| 250 | +#fancybox-bg-nw { |
| 251 | + top: -20px; |
| 252 | + left: -20px; |
| 253 | + background-image: url('fancybox.png'); |
| 254 | + background-position: -40px -122px; |
| 255 | +} |
| 256 | + |
| 257 | +#fancybox-title { |
| 258 | + font-family: Helvetica; |
| 259 | + font-size: 12px; |
| 260 | + z-index: 1102; |
| 261 | +} |
| 262 | + |
| 263 | +.fancybox-title-inside { |
| 264 | + padding-bottom: 10px; |
| 265 | + text-align: center; |
| 266 | + color: #333; |
| 267 | + background: #fff; |
| 268 | + position: relative; |
| 269 | +} |
| 270 | + |
| 271 | +.fancybox-title-outside { |
| 272 | + padding-top: 10px; |
| 273 | + color: #fff; |
| 274 | +} |
| 275 | + |
| 276 | +.fancybox-title-over { |
| 277 | + position: absolute; |
| 278 | + bottom: 0; |
| 279 | + left: 0; |
| 280 | + color: #FFF; |
| 281 | + text-align: left; |
| 282 | +} |
| 283 | + |
| 284 | +#fancybox-title-over { |
| 285 | + padding: 10px; |
| 286 | + background-image: url('fancy_title_over.png'); |
| 287 | + display: block; |
| 288 | +} |
| 289 | + |
| 290 | +.fancybox-title-float { |
| 291 | + position: absolute; |
| 292 | + left: 0; |
| 293 | + bottom: -20px; |
| 294 | + height: 32px; |
| 295 | +} |
| 296 | + |
| 297 | +#fancybox-title-float-wrap { |
| 298 | + border: none; |
| 299 | + border-collapse: collapse; |
| 300 | + width: auto; |
| 301 | +} |
| 302 | + |
| 303 | +#fancybox-title-float-wrap td { |
| 304 | + border: none; |
| 305 | + white-space: nowrap; |
| 306 | +} |
| 307 | + |
| 308 | +#fancybox-title-float-left { |
| 309 | + padding: 0 0 0 15px; |
| 310 | + background: url('fancybox.png') -40px -90px no-repeat; |
| 311 | +} |
| 312 | + |
| 313 | +#fancybox-title-float-main { |
| 314 | + color: #FFF; |
| 315 | + line-height: 29px; |
| 316 | + font-weight: bold; |
| 317 | + padding: 0 0 3px 0; |
| 318 | + background: url('fancybox-x.png') 0px -40px; |
| 319 | +} |
| 320 | + |
| 321 | +#fancybox-title-float-right { |
| 322 | + padding: 0 0 0 15px; |
| 323 | + background: url('fancybox.png') -55px -90px no-repeat; |
| 324 | +} |
| 325 | + |
| 326 | +/* IE6 */ |
| 327 | + |
| 328 | +.fancybox-ie6 #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_close.png', sizingMethod='scale'); } |
| 329 | + |
| 330 | +.fancybox-ie6 #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_left.png', sizingMethod='scale'); } |
| 331 | +.fancybox-ie6 #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_right.png', sizingMethod='scale'); } |
| 332 | + |
| 333 | +.fancybox-ie6 #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_over.png', sizingMethod='scale'); zoom: 1; } |
| 334 | +.fancybox-ie6 #fancybox-title-float-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_left.png', sizingMethod='scale'); } |
| 335 | +.fancybox-ie6 #fancybox-title-float-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_main.png', sizingMethod='scale'); } |
| 336 | +.fancybox-ie6 #fancybox-title-float-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_right.png', sizingMethod='scale'); } |
| 337 | + |
| 338 | +.fancybox-ie6 #fancybox-bg-w, .fancybox-ie6 #fancybox-bg-e, .fancybox-ie6 #fancybox-left, .fancybox-ie6 #fancybox-right, #fancybox-hide-sel-frame { |
| 339 | + height: expression(this.parentNode.clientHeight + "px"); |
| 340 | +} |
| 341 | + |
| 342 | +#fancybox-loading.fancybox-ie6 { |
| 343 | + position: absolute; margin-top: 0; |
| 344 | + top: expression( (-20 + (document.documentElement.clientHeight ? document.documentElement.clientHeight/2 : document.body.clientHeight/2 ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop )) + 'px'); |
| 345 | +} |
| 346 | + |
| 347 | +#fancybox-loading.fancybox-ie6 div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_loading.png', sizingMethod='scale'); } |
| 348 | + |
| 349 | +/* IE6, IE7, IE8 */ |
| 350 | + |
| 351 | +.fancybox-ie .fancybox-bg { background: transparent !important; } |
| 352 | + |
| 353 | +.fancybox-ie #fancybox-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_n.png', sizingMethod='scale'); } |
| 354 | +.fancybox-ie #fancybox-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_ne.png', sizingMethod='scale'); } |
| 355 | +.fancybox-ie #fancybox-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_e.png', sizingMethod='scale'); } |
| 356 | +.fancybox-ie #fancybox-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_se.png', sizingMethod='scale'); } |
| 357 | +.fancybox-ie #fancybox-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_s.png', sizingMethod='scale'); } |
| 358 | +.fancybox-ie #fancybox-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_sw.png', sizingMethod='scale'); } |
| 359 | +.fancybox-ie #fancybox-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_w.png', sizingMethod='scale'); } |
| 360 | +.fancybox-ie #fancybox-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_nw.png', sizingMethod='scale'); } |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.fancybox-1.3.4.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 361 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_se.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_se.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 362 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_over.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_over.png |
___________________________________________________________________ |
Added: svn:mime-type |
3 | 363 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.fancybox-1.3.4.pack.js |
— | — | @@ -0,0 +1,46 @@ |
| 2 | +/* |
| 3 | + * FancyBox - jQuery Plugin |
| 4 | + * Simple and fancy lightbox alternative |
| 5 | + * |
| 6 | + * Examples and documentation at: http://fancybox.net |
| 7 | + * |
| 8 | + * Copyright (c) 2008 - 2010 Janis Skarnelis |
| 9 | + * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. |
| 10 | + * |
| 11 | + * Version: 1.3.4 (11/11/2010) |
| 12 | + * Requires: jQuery v1.3+ |
| 13 | + * |
| 14 | + * Dual licensed under the MIT and GPL licenses: |
| 15 | + * http://www.opensource.org/licenses/mit-license.php |
| 16 | + * http://www.gnu.org/licenses/gpl.html |
| 17 | + */ |
| 18 | + |
| 19 | +;(function(b){var m,t,u,f,D,j,E,n,z,A,q=0,e={},o=[],p=0,d={},l=[],G=null,v=new Image,J=/\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,W=/[^\.]\.(swf)\s*$/i,K,L=1,y=0,s="",r,i,h=false,B=b.extend(b("<div/>")[0],{prop:0}),M=b.browser.msie&&b.browser.version<7&&!window.XMLHttpRequest,N=function(){t.hide();v.onerror=v.onload=null;G&&G.abort();m.empty()},O=function(){if(false===e.onError(o,q,e)){t.hide();h=false}else{e.titleShow=false;e.width="auto";e.height="auto";m.html('<p id="fancybox-error">The requested content cannot be loaded.<br />Please try again later.</p>'); |
| 20 | +F()}},I=function(){var a=o[q],c,g,k,C,P,w;N();e=b.extend({},b.fn.fancybox.defaults,typeof b(a).data("fancybox")=="undefined"?e:b(a).data("fancybox"));w=e.onStart(o,q,e);if(w===false)h=false;else{if(typeof w=="object")e=b.extend(e,w);k=e.title||(a.nodeName?b(a).attr("title"):a.title)||"";if(a.nodeName&&!e.orig)e.orig=b(a).children("img:first").length?b(a).children("img:first"):b(a);if(k===""&&e.orig&&e.titleFromAlt)k=e.orig.attr("alt");c=e.href||(a.nodeName?b(a).attr("href"):a.href)||null;if(/^(?:javascript)/i.test(c)|| |
| 21 | +c=="#")c=null;if(e.type){g=e.type;if(!c)c=e.content}else if(e.content)g="html";else if(c)g=c.match(J)?"image":c.match(W)?"swf":b(a).hasClass("iframe")?"iframe":c.indexOf("#")===0?"inline":"ajax";if(g){if(g=="inline"){a=c.substr(c.indexOf("#"));g=b(a).length>0?"inline":"ajax"}e.type=g;e.href=c;e.title=k;if(e.autoDimensions)if(e.type=="html"||e.type=="inline"||e.type=="ajax"){e.width="auto";e.height="auto"}else e.autoDimensions=false;if(e.modal){e.overlayShow=true;e.hideOnOverlayClick=false;e.hideOnContentClick= |
| 22 | +false;e.enableEscapeButton=false;e.showCloseButton=false}e.padding=parseInt(e.padding,10);e.margin=parseInt(e.margin,10);m.css("padding",e.padding+e.margin);b(".fancybox-inline-tmp").unbind("fancybox-cancel").bind("fancybox-change",function(){b(this).replaceWith(j.children())});switch(g){case "html":m.html(e.content);F();break;case "inline":if(b(a).parent().is("#fancybox-content")===true){h=false;break}b('<div class="fancybox-inline-tmp" />').hide().insertBefore(b(a)).bind("fancybox-cleanup",function(){b(this).replaceWith(j.children())}).bind("fancybox-cancel", |
| 23 | +function(){b(this).replaceWith(m.children())});b(a).appendTo(m);F();break;case "image":h=false;b.fancybox.showActivity();v=new Image;v.onerror=function(){O()};v.onload=function(){h=true;v.onerror=v.onload=null;e.width=v.width;e.height=v.height;b("<img />").attr({id:"fancybox-img",src:v.src,alt:e.title}).appendTo(m);Q()};v.src=c;break;case "swf":e.scrolling="no";C='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+e.width+'" height="'+e.height+'"><param name="movie" value="'+c+ |
| 24 | +'"></param>';P="";b.each(e.swf,function(x,H){C+='<param name="'+x+'" value="'+H+'"></param>';P+=" "+x+'="'+H+'"'});C+='<embed src="'+c+'" type="application/x-shockwave-flash" width="'+e.width+'" height="'+e.height+'"'+P+"></embed></object>";m.html(C);F();break;case "ajax":h=false;b.fancybox.showActivity();e.ajax.win=e.ajax.success;G=b.ajax(b.extend({},e.ajax,{url:c,data:e.ajax.data||{},error:function(x){x.status>0&&O()},success:function(x,H,R){if((typeof R=="object"?R:G).status==200){if(typeof e.ajax.win== |
| 25 | +"function"){w=e.ajax.win(c,x,H,R);if(w===false){t.hide();return}else if(typeof w=="string"||typeof w=="object")x=w}m.html(x);F()}}}));break;case "iframe":Q()}}else O()}},F=function(){var a=e.width,c=e.height;a=a.toString().indexOf("%")>-1?parseInt((b(window).width()-e.margin*2)*parseFloat(a)/100,10)+"px":a=="auto"?"auto":a+"px";c=c.toString().indexOf("%")>-1?parseInt((b(window).height()-e.margin*2)*parseFloat(c)/100,10)+"px":c=="auto"?"auto":c+"px";m.wrapInner('<div style="width:'+a+";height:"+c+ |
| 26 | +";overflow: "+(e.scrolling=="auto"?"auto":e.scrolling=="yes"?"scroll":"hidden")+';position:relative;"></div>');e.width=m.width();e.height=m.height();Q()},Q=function(){var a,c;t.hide();if(f.is(":visible")&&false===d.onCleanup(l,p,d)){b.event.trigger("fancybox-cancel");h=false}else{h=true;b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");f.is(":visible")&&d.titlePosition!=="outside"&&f.css("height",f.height());l=o;p=q;d=e;if(d.overlayShow){u.css({"background-color":d.overlayColor, |
| 27 | +opacity:d.overlayOpacity,cursor:d.hideOnOverlayClick?"pointer":"auto",height:b(document).height()});if(!u.is(":visible")){M&&b("select:not(#fancybox-tmp select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("fancybox-cleanup",function(){this.style.visibility="inherit"});u.show()}}else u.hide();i=X();s=d.title||"";y=0;n.empty().removeAttr("style").removeClass();if(d.titleShow!==false){if(b.isFunction(d.titleFormat))a=d.titleFormat(s,l,p,d);else a=s&&s.length? |
| 28 | +d.titlePosition=="float"?'<table id="fancybox-title-float-wrap" cellpadding="0" cellspacing="0"><tr><td id="fancybox-title-float-left"></td><td id="fancybox-title-float-main">'+s+'</td><td id="fancybox-title-float-right"></td></tr></table>':'<div id="fancybox-title-'+d.titlePosition+'">'+s+"</div>":false;s=a;if(!(!s||s==="")){n.addClass("fancybox-title-"+d.titlePosition).html(s).appendTo("body").show();switch(d.titlePosition){case "inside":n.css({width:i.width-d.padding*2,marginLeft:d.padding,marginRight:d.padding}); |
| 29 | +y=n.outerHeight(true);n.appendTo(D);i.height+=y;break;case "over":n.css({marginLeft:d.padding,width:i.width-d.padding*2,bottom:d.padding}).appendTo(D);break;case "float":n.css("left",parseInt((n.width()-i.width-40)/2,10)*-1).appendTo(f);break;default:n.css({width:i.width-d.padding*2,paddingLeft:d.padding,paddingRight:d.padding}).appendTo(f)}}}n.hide();if(f.is(":visible")){b(E.add(z).add(A)).hide();a=f.position();r={top:a.top,left:a.left,width:f.width(),height:f.height()};c=r.width==i.width&&r.height== |
| 30 | +i.height;j.fadeTo(d.changeFade,0.3,function(){var g=function(){j.html(m.contents()).fadeTo(d.changeFade,1,S)};b.event.trigger("fancybox-change");j.empty().removeAttr("filter").css({"border-width":d.padding,width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2});if(c)g();else{B.prop=0;b(B).animate({prop:1},{duration:d.changeSpeed,easing:d.easingChange,step:T,complete:g})}})}else{f.removeAttr("style");j.css("border-width",d.padding);if(d.transitionIn=="elastic"){r=V();j.html(m.contents()); |
| 31 | +f.show();if(d.opacity)i.opacity=0;B.prop=0;b(B).animate({prop:1},{duration:d.speedIn,easing:d.easingIn,step:T,complete:S})}else{d.titlePosition=="inside"&&y>0&&n.show();j.css({width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2}).html(m.contents());f.css(i).fadeIn(d.transitionIn=="none"?0:d.speedIn,S)}}}},Y=function(){if(d.enableEscapeButton||d.enableKeyboardNav)b(document).bind("keydown.fb",function(a){if(a.keyCode==27&&d.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if((a.keyCode== |
| 32 | +37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.get(0).style.removeAttribute("filter");f.get(0).style.removeAttribute("filter")}e.autoDimensions&&j.css("height","auto");f.css("height","auto"); |
| 33 | +s&&s.length&&n.show();d.showCloseButton&&E.show();Y();d.hideOnContentClick&&j.bind("click",b.fancybox.close);d.hideOnOverlayClick&&u.bind("click",b.fancybox.close);b(window).bind("resize.fb",b.fancybox.resize);d.centerOnScroll&&b(window).bind("scroll.fb",b.fancybox.center);if(d.type=="iframe")b('<iframe id="fancybox-frame" name="fancybox-frame'+(new Date).getTime()+'" frameborder="0" hspace="0" '+(b.browser.msie?'allowtransparency="true""':"")+' scrolling="'+e.scrolling+'" src="'+d.href+'"></iframe>').appendTo(j); |
| 34 | +f.show();h=false;b.fancybox.center();d.onComplete(l,p,d);var a,c;if(l.length-1>p){a=l[p+1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}if(p>0){a=l[p-1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}},T=function(a){var c={width:parseInt(r.width+(i.width-r.width)*a,10),height:parseInt(r.height+(i.height-r.height)*a,10),top:parseInt(r.top+(i.top-r.top)*a,10),left:parseInt(r.left+(i.left-r.left)*a,10)};if(typeof i.opacity!=="undefined")c.opacity=a<0.5?0.5:a;f.css(c); |
| 35 | +j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function(){return[b(window).width()-d.margin*2,b(window).height()-d.margin*2,b(document).scrollLeft()+d.margin,b(document).scrollTop()+d.margin]},X=function(){var a=U(),c={},g=d.autoScale,k=d.padding*2;c.width=d.width.toString().indexOf("%")>-1?parseInt(a[0]*parseFloat(d.width)/100,10):d.width+k;c.height=d.height.toString().indexOf("%")>-1?parseInt(a[1]*parseFloat(d.height)/100,10):d.height+k;if(g&&(c.width>a[0]||c.height>a[1]))if(e.type== |
| 36 | +"image"||e.type=="swf"){g=d.width/d.height;if(c.width>a[0]){c.width=a[0];c.height=parseInt((c.width-k)/g+k,10)}if(c.height>a[1]){c.height=a[1];c.width=parseInt((c.height-k)*g+k,10)}}else{c.width=Math.min(c.width,a[0]);c.height=Math.min(c.height,a[1])}c.top=parseInt(Math.max(a[3]-20,a[3]+(a[1]-c.height-40)*0.5),10);c.left=parseInt(Math.max(a[2]-20,a[2]+(a[0]-c.width-40)*0.5),10);return c},V=function(){var a=e.orig?b(e.orig):false,c={};if(a&&a.length){c=a.offset();c.top+=parseInt(a.css("paddingTop"), |
| 37 | +10)||0;c.left+=parseInt(a.css("paddingLeft"),10)||0;c.top+=parseInt(a.css("border-top-width"),10)||0;c.left+=parseInt(a.css("border-left-width"),10)||0;c.width=a.width();c.height=a.height();c={width:c.width+d.padding*2,height:c.height+d.padding*2,top:c.top-d.padding-20,left:c.left-d.padding-20}}else{a=U();c={width:d.padding*2,height:d.padding*2,top:parseInt(a[3]+a[1]*0.5,10),left:parseInt(a[2]+a[0]*0.5,10)}}return c},Z=function(){if(t.is(":visible")){b("div",t).css("top",L*-40+"px");L=(L+1)%12}else clearInterval(K)}; |
| 38 | +b.fn.fancybox=function(a){if(!b(this).length)return this;b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(c){c.preventDefault();if(!h){h=true;b(this).blur();o=[];q=0;c=b(this).attr("rel")||"";if(!c||c==""||c==="nofollow")o.push(this);else{o=b("a[rel="+c+"], area[rel="+c+"]");q=o.index(this)}I()}});return this};b.fancybox=function(a,c){var g;if(!h){h=true;g=typeof c!=="undefined"?c:{};o=[];q=parseInt(g.index,10)||0;if(b.isArray(a)){for(var k= |
| 39 | +0,C=a.length;k<C;k++)if(typeof a[k]=="object")b(a[k]).data("fancybox",b.extend({},g,a[k]));else a[k]=b({}).data("fancybox",b.extend({content:a[k]},g));o=jQuery.merge(o,a)}else{if(typeof a=="object")b(a).data("fancybox",b.extend({},g,a));else a=b({}).data("fancybox",b.extend({content:a},g));o.push(a)}if(q>o.length||q<0)q=0;I()}};b.fancybox.showActivity=function(){clearInterval(K);t.show();K=setInterval(Z,66)};b.fancybox.hideActivity=function(){t.hide()};b.fancybox.next=function(){return b.fancybox.pos(p+ |
| 40 | +1)};b.fancybox.prev=function(){return b.fancybox.pos(p-1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a);o=l;if(a>-1&&a<l.length){q=a;I()}else if(d.cyclic&&l.length>1){q=a>=l.length?0:l.length-1;I()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");N();e.onCancel(o,q,e);h=false}};b.fancybox.close=function(){function a(){u.fadeOut("fast");n.empty().hide();f.hide();b.event.trigger("fancybox-cleanup");j.empty();d.onClosed(l,p,d);l=e=[];p=q=0;d=e={};h=false}if(!(h||f.is(":hidden"))){h= |
| 41 | +true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1; |
| 42 | +b(B).animate({prop:0},{duration:d.speedOut,easing:d.easingOut,step:T,complete:a})}else f.fadeOut(d.transitionOut=="none"?0:d.speedOut,a)}}};b.fancybox.resize=function(){u.is(":visible")&&u.css("height",b(document).height());b.fancybox.center(true)};b.fancybox.center=function(a){var c,g;if(!h){g=a===true?1:0;c=U();!g&&(f.width()>c[0]||f.height()>c[1])||f.stop().animate({top:parseInt(Math.max(c[3]-20,c[3]+(c[1]-j.height()-40)*0.5-d.padding)),left:parseInt(Math.max(c[2]-20,c[2]+(c[0]-j.width()-40)*0.5- |
| 43 | +d.padding))},typeof a=="number"?a:200)}};b.fancybox.init=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('<div id="fancybox-tmp"></div>'),t=b('<div id="fancybox-loading"><div></div></div>'),u=b('<div id="fancybox-overlay"></div>'),f=b('<div id="fancybox-wrap"></div>'));D=b('<div id="fancybox-outer"></div>').append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>').appendTo(f); |
| 44 | +D.append(j=b('<div id="fancybox-content"></div>'),E=b('<a id="fancybox-close"></a>'),n=b('<div id="fancybox-title"></div>'),z=b('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'),A=b('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>'));E.click(b.fancybox.close);t.click(b.fancybox.cancel);z.click(function(a){a.preventDefault();b.fancybox.prev()});A.click(function(a){a.preventDefault();b.fancybox.next()}); |
| 45 | +b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('<iframe id="fancybox-hide-sel-frame" src="'+(/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank")+'" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(D)}}}; |
| 46 | +b.fn.fancybox.defaults={padding:10,margin:40,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.7,overlayColor:"#777",titleShow:true,titlePosition:"float",titleFormat:null,titleFromAlt:false,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast",easingIn:"swing", |
| 47 | +easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery); |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.fancybox-1.3.4.pack.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 48 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_loading.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_loading.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 49 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_right.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_right.png |
___________________________________________________________________ |
Added: svn:mime-type |
3 | 50 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.mousewheel-3.0.4.pack.js |
— | — | @@ -0,0 +1,14 @@ |
| 2 | +/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net) |
| 3 | +* Licensed under the MIT License (LICENSE.txt). |
| 4 | +* |
| 5 | +* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. |
| 6 | +* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. |
| 7 | +* Thanks to: Seamus Leahy for adding deltaX and deltaY |
| 8 | +* |
| 9 | +* Version: 3.0.4 |
| 10 | +* |
| 11 | +* Requires: 1.2.2+ |
| 12 | +*/ |
| 13 | + |
| 14 | +(function(d){function g(a){var b=a||window.event,i=[].slice.call(arguments,1),c=0,h=0,e=0;a=d.event.fix(b);a.type="mousewheel";if(a.wheelDelta)c=a.wheelDelta/120;if(a.detail)c=-a.detail/3;e=c;if(b.axis!==undefined&&b.axis===b.HORIZONTAL_AXIS){e=0;h=-1*c}if(b.wheelDeltaY!==undefined)e=b.wheelDeltaY/120;if(b.wheelDeltaX!==undefined)h=-1*b.wheelDeltaX/120;i.unshift(a,c,h,e);return d.event.handle.apply(this,i)}var f=["DOMMouseScroll","mousewheel"];d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a= |
| 15 | +f.length;a;)this.addEventListener(f[--a],g,false);else this.onmousewheel=g},teardown:function(){if(this.removeEventListener)for(var a=f.length;a;)this.removeEventListener(f[--a],g,false);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery); |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/jquery.mousewheel-3.0.4.pack.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 16 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_nav_right.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_nav_right.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 17 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_main.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_main.png |
___________________________________________________________________ |
Added: svn:mime-type |
3 | 18 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/blank.gif |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/blank.gif |
___________________________________________________________________ |
Added: svn:mime-type |
4 | 19 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_nw.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_nw.png |
___________________________________________________________________ |
Added: svn:mime-type |
5 | 20 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_e.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_shadow_e.png |
___________________________________________________________________ |
Added: svn:mime-type |
6 | 21 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancybox.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancybox.png |
___________________________________________________________________ |
Added: svn:mime-type |
7 | 22 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_left.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/fancybox/fancy_title_left.png |
___________________________________________________________________ |
Added: svn:mime-type |
8 | 23 | + image/png |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.contests.js |
— | — | @@ -0,0 +1,57 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +(function( $, mw ) { $( document ).ready( function() { |
| 11 | + |
| 12 | + function deleteContest( options, successCallback, failCallback ) { |
| 13 | + $.post( |
| 14 | + wgScriptPath + '/api.php', |
| 15 | + { |
| 16 | + 'action': 'deletecontest', |
| 17 | + 'format': 'json', |
| 18 | + 'ids': options.id, |
| 19 | + 'token': options.token |
| 20 | + }, |
| 21 | + function( data ) { |
| 22 | + if ( data.success ) { |
| 23 | + successCallback(); |
| 24 | + } else { |
| 25 | + failCallback( mw.msg( 'contest-special-delete-failed' ) ); |
| 26 | + } |
| 27 | + } |
| 28 | + ); |
| 29 | + } |
| 30 | + |
| 31 | + $( '.contest-delete' ).click( function() { |
| 32 | + $this = $( this ); |
| 33 | + |
| 34 | + if ( confirm( mw.msg( 'contest-special-confirm-delete' ) ) ) { |
| 35 | + deleteContest( |
| 36 | + { |
| 37 | + id: $this.attr( 'data-contest-id' ), |
| 38 | + token: $this.attr( 'data-contest-token' ) |
| 39 | + }, |
| 40 | + function() { |
| 41 | + $this.closest( 'tr' ).slideUp( 'slow', function() { |
| 42 | + $( this ).remove(); |
| 43 | + |
| 44 | + if ( $( '.contests-table tr' ).length < 2 ) { |
| 45 | + $( '.contests-table' ).remove(); |
| 46 | + $( '.contests-title' ).remove(); |
| 47 | + } |
| 48 | + } ); |
| 49 | + }, |
| 50 | + function( error ) { |
| 51 | + alert( error ); |
| 52 | + } |
| 53 | + ); |
| 54 | + } |
| 55 | + return false; |
| 56 | + } ); |
| 57 | + |
| 58 | +} ); })( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.contests.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 59 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.editcontest.js |
— | — | @@ -0,0 +1,173 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +(function( $, mw ) { |
| 11 | + |
| 12 | + function addChallengeToRemove( id ) { |
| 13 | + if ( !isNaN( id ) ) { |
| 14 | + var currentVal = $( '#delete-challenges' ).val(); |
| 15 | + |
| 16 | + var currentIds = currentVal !== '' ? currentVal.split( '|' ) : []; |
| 17 | + currentIds.push( id ); |
| 18 | + |
| 19 | + $( '#delete-challenges' ).val( currentIds.join( '|' ) ); |
| 20 | + } |
| 21 | + } |
| 22 | + |
| 23 | + $.fn.mwChallenge = function( options ) { |
| 24 | + |
| 25 | + var _this = this; |
| 26 | + var $this = $( this ); |
| 27 | + this.options = options; |
| 28 | + |
| 29 | + this.titleInput = null; |
| 30 | + this.textInput = null; |
| 31 | + this.deleteButton = null; |
| 32 | + |
| 33 | + this.remove = function() { |
| 34 | + addChallengeToRemove( $this.attr( 'data-challenge-id' ) ); |
| 35 | + |
| 36 | + $tr = $this.closest( 'tr' ); |
| 37 | + $tr.slideUp( 'fast', function() { $tr.remove(); } ); |
| 38 | + }; |
| 39 | + |
| 40 | + this.init = function() { |
| 41 | + $this.html( '' ); |
| 42 | + |
| 43 | + this.titleInput = $( '<input />' ).attr( { |
| 44 | + 'type': 'text', |
| 45 | + 'name': 'contest-challenge-' + $this.attr( 'data-challenge-id' ), |
| 46 | + 'size': 45 |
| 47 | + } ).val( $this.attr( 'data-challenge-title' ) ); |
| 48 | + |
| 49 | + $this.append( |
| 50 | + $( '<div />' ).html( |
| 51 | + $( '<label />' ) |
| 52 | + .text( mw.msg( 'contest-edit-challenge-title' ) ) |
| 53 | + .attr( 'for', 'contest-challenge-' + $this.attr( 'data-challenge-id' ) ) |
| 54 | + ).append( ' ' ).append( this.titleInput ) |
| 55 | + ); |
| 56 | + |
| 57 | + this.onelineInput = $( '<input />' ).attr( { |
| 58 | + 'type': 'text', |
| 59 | + 'name': 'challenge-oneline-' + $this.attr( 'data-challenge-id' ), |
| 60 | + 'size': 45, |
| 61 | + 'style': 'margin-top: 3px' |
| 62 | + } ).val( $this.attr( 'data-challenge-oneline' ) ); |
| 63 | + |
| 64 | + $this.append( |
| 65 | + $( '<div />' ).html( |
| 66 | + $( '<label />' ) |
| 67 | + .text( mw.msg( 'contest-edit-challenge-oneline' ) ) |
| 68 | + .attr( { 'for': 'contest-oneline-' + $this.attr( 'data-challenge-id' ) } ) |
| 69 | + ).append( ' ' ).append( this.onelineInput ) |
| 70 | + ); |
| 71 | + |
| 72 | + this.textInput = $( '<textarea />' ).attr( { |
| 73 | + 'name': 'challenge-text-' + $this.attr( 'data-challenge-id' ) |
| 74 | + } ).val( $this.attr( 'data-challenge-text' ) ); |
| 75 | + |
| 76 | + $this.append( |
| 77 | + $( '<div />' ).html( |
| 78 | + $( '<label />' ) |
| 79 | + .text( mw.msg( 'contest-edit-challenge-text' ) ) |
| 80 | + .attr( 'for', 'challenge-text-' + $this.attr( 'data-challenge-id' ) ) |
| 81 | + ).append( '<br />' ).append( this.textInput ) |
| 82 | + ); |
| 83 | + |
| 84 | + this.deleteButton = $( '<button />' ) |
| 85 | + .button( { 'label': mw.msg( 'contest-edit-delete' ) } ) |
| 86 | + .click( function() { |
| 87 | + if ( confirm( mw.msg( 'contest-edit-confirm-delete' ) ) ) { |
| 88 | + _this.remove(); |
| 89 | + return false; |
| 90 | + } |
| 91 | + } ); |
| 92 | + |
| 93 | + $this.append( this.deleteButton ); |
| 94 | + }; |
| 95 | + |
| 96 | + this.init(); |
| 97 | + |
| 98 | + return this; |
| 99 | + |
| 100 | + }; |
| 101 | + |
| 102 | + var newNr = 0; |
| 103 | + var $table = null; |
| 104 | + |
| 105 | + function getNewChallengeMessage() { |
| 106 | + return mw.msg( 'contest-edit-add-' + ( $( '.contest-challenge-input' ).size() === 0 ? 'first' : 'another' ) ); |
| 107 | + } |
| 108 | + |
| 109 | + function addChallenge( challenge ) { |
| 110 | + $challenge = $( '<div />' ).attr( { |
| 111 | + 'class': 'contest-challenge-input', |
| 112 | + 'data-challenge-id': challenge.id, |
| 113 | + 'data-challenge-title': challenge.title, |
| 114 | + 'data-challenge-text': challenge.text, |
| 115 | + 'data-challenge-oneline': challenge.oneline |
| 116 | + } ); |
| 117 | + |
| 118 | + $tr = $( '<tr />' ); |
| 119 | + |
| 120 | + $tr.append( $( '<td />' ) ); |
| 121 | + |
| 122 | + $tr.append( $( '<td />' ).html( $challenge ).append( '<hr />' ) ); |
| 123 | + |
| 124 | + $( '.add-new-challenge' ).before( $tr ); |
| 125 | + |
| 126 | + $challenge.mwChallenge(); |
| 127 | + } |
| 128 | + |
| 129 | + $( document ).ready( function() { |
| 130 | + |
| 131 | + $( '#cancelEdit' ).click( function() { |
| 132 | + window.location = $( this ).attr( 'target-url' ); |
| 133 | + } ); |
| 134 | + |
| 135 | + $table = $( '#contest-name-field' ).closest( 'tbody' ); |
| 136 | + |
| 137 | + $( '#bodyContent' ).find( '[type="submit"]' ).button(); |
| 138 | + |
| 139 | + $table.append( '<tr><td colspan="2"><hr /></td></tr>' ); |
| 140 | + |
| 141 | + $addNew = $( '<button />' ).button( { 'label': getNewChallengeMessage() } ).click( function() { |
| 142 | + addChallenge( { |
| 143 | + 'id': 'new-' + newNr++ , |
| 144 | + 'title': '', |
| 145 | + 'text': '' |
| 146 | + } ); |
| 147 | + |
| 148 | + $( this ).button( { 'label': getNewChallengeMessage() } ); |
| 149 | + |
| 150 | + return false; |
| 151 | + } ); |
| 152 | + |
| 153 | + $table.append( $( '<tr />' ).attr( 'class', 'add-new-challenge' ).html( $( '<td />' ) ).append( $( '<td />' ).html( $addNew ) ) ); |
| 154 | + |
| 155 | + $table.append( '<tr><td colspan="2"><hr /></td></tr>' ); |
| 156 | + |
| 157 | + $( '.contest-challenge' ).each( function( index, domElement ) { |
| 158 | + $this = $( domElement ); |
| 159 | + addChallenge( { |
| 160 | + 'id': $this.attr( 'data-challenge-id' ), |
| 161 | + 'title': $this.attr( 'data-challenge-title' ), |
| 162 | + 'text': $this.attr( 'data-challenge-text' ), |
| 163 | + 'oneline': $this.attr( 'data-challenge-oneline' ), |
| 164 | + } ); |
| 165 | + } ); |
| 166 | + |
| 167 | + $( '#contest-edit-end' ).datetimepicker( { |
| 168 | + minDate: new Date(), |
| 169 | + dateFormat: 'yy-mm-dd' |
| 170 | + } ); |
| 171 | + |
| 172 | + } ); |
| 173 | + |
| 174 | +})( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.editcontest.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 175 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.signup.js |
— | — | @@ -0,0 +1,39 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +(function( $, mw ) { |
| 11 | + |
| 12 | + $( document ).ready( function() { |
| 13 | + |
| 14 | + var contestConfig = mw.config.get( 'ContestConfig' ); |
| 15 | + |
| 16 | + $( '.mw-htmlform-submit' ).button(); |
| 17 | + |
| 18 | + $rules = $( '#contest-rules' ); |
| 19 | + |
| 20 | + $div = $( '<div />' ).attr( { |
| 21 | + 'style': 'display:none' |
| 22 | + } ).html( $( '<div />' ).attr( { 'id': 'contest-rules-div' } ).html( contestConfig['rules_page'] ) ); |
| 23 | + |
| 24 | + $a = $( "label[for='contest-rules']" ).find( 'a' ); |
| 25 | + $a.attr( { 'href': '#contest-rules-div' } ); |
| 26 | + |
| 27 | + $rules.closest( 'td' ).append( $div ); |
| 28 | + |
| 29 | + $a.fancybox( { |
| 30 | + 'width' : '85%', |
| 31 | + 'height' : '85%', |
| 32 | + 'transitionIn' : 'none', |
| 33 | + 'transitionOut' : 'none', |
| 34 | + 'type' : 'inline', |
| 35 | + 'autoDimensions': false |
| 36 | + } ); |
| 37 | + |
| 38 | + } ); |
| 39 | + |
| 40 | +})( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.signup.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 41 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.ui.timepicker.css |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +/* css for timepicker */ |
| 3 | +.ui-timepicker-div .ui-widget-header{ margin-bottom: 8px; } |
| 4 | +.ui-timepicker-div dl{ text-align: left; } |
| 5 | +.ui-timepicker-div dl dt{ height: 25px; } |
| 6 | +.ui-timepicker-div dl dd{ margin: -25px 0 10px 65px; } |
| 7 | +.ui-timepicker-div td { font-size: 90%; } |
\ No newline at end of file |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.ui.timepicker.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 8 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.contestChallenges.js |
— | — | @@ -0,0 +1,100 @@ |
| 2 | +/** |
| 3 | + * JavasSript for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +( function ( $, mw ) { $.fn.contestChallenges = function( challenges, config ) { |
| 11 | + |
| 12 | + this.challenges = challenges; |
| 13 | + this.config = config; |
| 14 | + |
| 15 | + var _this = this; |
| 16 | + var $this = $( this ); |
| 17 | + |
| 18 | + this.challengesList = null; |
| 19 | + |
| 20 | + this.addChallenge = function( challenge ) { |
| 21 | + this.challengesList |
| 22 | + .append( |
| 23 | + $( '<li class="mw-codechallenge-box-outside"></li>' ) |
| 24 | + .click( function( e ) { |
| 25 | + var box = $(this); |
| 26 | + if ( !box.hasClass( 'mw-codechallenge-box-selected' ) ) { |
| 27 | + $( '.mw-codechallenge-popup' ).not( box ).fadeOut( 'fast' ); |
| 28 | + $( '.mw-codechallenge-box-selected' ).removeClass( 'mw-codechallenge-box-selected' ); |
| 29 | + box |
| 30 | + .addClass( 'mw-codechallenge-box-selected' ) |
| 31 | + .find( '.mw-codechallenge-popup' ) |
| 32 | + .fadeIn( 'fast' ); |
| 33 | + $(document).one( 'click', function() { |
| 34 | + box |
| 35 | + .removeClass( 'mw-codechallenge-box-selected' ) |
| 36 | + .find( '.mw-codechallenge-popup' ) |
| 37 | + .fadeOut( 'fast' ); |
| 38 | + } ); |
| 39 | + e.stopPropagation(); |
| 40 | + return false; |
| 41 | + } |
| 42 | + } ) |
| 43 | + .append( |
| 44 | + $( '<div class="mw-codechallenge-box-inside"></div>' ) |
| 45 | + .append( '<div class="mw-codechallenge-box-top"></div>' ) |
| 46 | + .append( |
| 47 | + $('<div class="mw-codechallenge-box-text"></div>' ) |
| 48 | + .append( |
| 49 | + $( '<h4 class="mw-codechallenge-box-title"></h4>' ) |
| 50 | + .text( challenge.title ) |
| 51 | + ) |
| 52 | + .append( |
| 53 | + $( '<p class="mw-codechallenge-box-desc">' ) |
| 54 | + .text( challenge.oneline ) |
| 55 | + ) |
| 56 | + ) |
| 57 | + .append( |
| 58 | + $( '<div class="mw-codechallenge-popup"><div>' ) |
| 59 | + .click( function() { |
| 60 | + return false; |
| 61 | + } ) |
| 62 | + .append( '<div class="mw-codechallenge-popup-callout"></div>' ) |
| 63 | + .append( challenge.text ) |
| 64 | + .append( |
| 65 | + $( '<div class="mw-codechallenge-popup-buttons"></div>' ) |
| 66 | + .append( |
| 67 | + $( '<button class="ui-button-green"></button>' ) |
| 68 | + // TODO: Internationalize this! |
| 69 | + .text( mw.msg( 'contest-welcome-accept-challenge' ) ) |
| 70 | + .button() |
| 71 | + .click( function() { |
| 72 | + window.location = challenge.target; |
| 73 | + } ) |
| 74 | + ) |
| 75 | + ) |
| 76 | + ) |
| 77 | + ) |
| 78 | + ); |
| 79 | + } |
| 80 | + |
| 81 | + this.initChallenges = function() { |
| 82 | + this.challengesList = $( '<ul />' ).attr( 'id', 'contest-challenges-list' ); |
| 83 | + |
| 84 | + for ( var i in this.challenges ) { |
| 85 | + this.addChallenge( this.challenges[i] ); |
| 86 | + } |
| 87 | + }; |
| 88 | + |
| 89 | + this.init = function() { |
| 90 | + $this.html( $( '<h3 />' ).text( mw.msg( 'contest-welcome-select-header' ) ) ); |
| 91 | + |
| 92 | + this.initChallenges(); |
| 93 | + |
| 94 | + $this.append( this.challengesList ); |
| 95 | + }; |
| 96 | + |
| 97 | + this.init(); |
| 98 | + |
| 99 | + return this; |
| 100 | + |
| 101 | +}; } )( window.jQuery, window.mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.contestChallenges.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 102 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.welcome.css |
— | — | @@ -0,0 +1,141 @@ |
| 2 | +/** |
| 3 | + * CSS for the Contest MediaWiki extension. |
| 4 | + * @see https://www.mediawiki.org/wiki/Extension:Contest |
| 5 | + * |
| 6 | + * @licence GNU GPL v3 or later |
| 7 | + * @author Jeroen De Dauw <jeroendedauw at gmail dot com> |
| 8 | + */ |
| 9 | + |
| 10 | +/* dialog type selection */ |
| 11 | +#contest-challenges-list { |
| 12 | + float: left; |
| 13 | + width: 354px; |
| 14 | + margin: 1.5em 0 0.5em 0; |
| 15 | + margin-top: 1.5em; |
| 16 | +} |
| 17 | +#contest-challenges-list ul { |
| 18 | + list-style: none; |
| 19 | + list-style-image: none; |
| 20 | + margin: 0; |
| 21 | + padding: 0; |
| 22 | +} |
| 23 | +/* IGNORED BY IE6 */ |
| 24 | +#contest-challenges-list ul > li { |
| 25 | + display: inline-block; |
| 26 | +} |
| 27 | +.mw-codechallenge-box-outside { |
| 28 | + list-style: none; |
| 29 | + list-style-image: none; |
| 30 | + position: relative; |
| 31 | + display: block; |
| 32 | + width: 354px; |
| 33 | + padding: 0; |
| 34 | + margin: 0 0 1em 0; |
| 35 | + /* @embed */ |
| 36 | + background-image: url(images/box-sprite.png); |
| 37 | + background-position: 0 0; |
| 38 | + background-repeat: repeat-y; |
| 39 | + cursor: pointer; |
| 40 | +} |
| 41 | +.mw-codechallenge-box-outside:hover, |
| 42 | +.mw-codechallenge-box-outside:focus { |
| 43 | + background-position: -354px 0; |
| 44 | +} |
| 45 | +.mw-codechallenge-box-selected { |
| 46 | + background-position: -708px 0 !important; |
| 47 | +} |
| 48 | +.mw-codechallenge-box-outside .mw-codechallenge-box-inside { |
| 49 | + /* @embed */ |
| 50 | + background-image: url(images/box-bottom-sprite.png); |
| 51 | + background-position: 0 bottom; |
| 52 | + background-repeat: no-repeat; |
| 53 | +} |
| 54 | +.mw-codechallenge-box-outside:hover .mw-codechallenge-box-inside, |
| 55 | +.mw-codechallenge-box-outside:focus .mw-codechallenge-box-inside { |
| 56 | + background-position: -354px bottom; |
| 57 | +} |
| 58 | +.mw-codechallenge-box-selected .mw-codechallenge-box-inside { |
| 59 | + background-position: -708px bottom !important; |
| 60 | +} |
| 61 | +.mw-codechallenge-box-outside .mw-codechallenge-box-top { |
| 62 | + height: 10px; |
| 63 | + /* @embed */ |
| 64 | + background-image: url(images/box-top-sprite.png); |
| 65 | + background-position: 0 top; |
| 66 | + background-repeat: no-repeat; |
| 67 | +} |
| 68 | +.mw-codechallenge-box-outside:hover .mw-codechallenge-box-top, |
| 69 | +.mw-codechallenge-box-outside:focus .mw-codechallenge-box-top { |
| 70 | + background-position: -354px top; |
| 71 | +} |
| 72 | +.mw-codechallenge-box-selected .mw-codechallenge-box-top { |
| 73 | + background-position: -708px top !important; |
| 74 | +} |
| 75 | +.mw-codechallenge-box-text { |
| 76 | + display: block; |
| 77 | + margin-top: -10px; |
| 78 | + padding: 1em 1.5em 1.25em 1.5em; |
| 79 | + text-decoration: none; |
| 80 | + outline: none; |
| 81 | + /* @embed */ |
| 82 | + background-image: url(images/arrow.png); |
| 83 | + background-position: right center; |
| 84 | + background-repeat: no-repeat; |
| 85 | +} |
| 86 | +.mw-codechallenge-box-outside:hover .mw-codechallenge-box-text, |
| 87 | +.mw-codechallenge-box-outside:focus .mw-codechallenge-box-text, |
| 88 | +.mw-codechallenge-box-selected .mw-codechallenge-box-text { |
| 89 | + /* @embed */ |
| 90 | + background-image: url(images/arrow-hover.png); |
| 91 | + text-decoration:none; |
| 92 | +} |
| 93 | +.mw-codechallenge-box-title { |
| 94 | + color: white; |
| 95 | + margin: 0; |
| 96 | + padding: 0 32px 0 0; |
| 97 | +} |
| 98 | +.mw-codechallenge-box-desc { |
| 99 | + color: white; |
| 100 | + margin: 0; |
| 101 | + padding: 0 32px 0 0; |
| 102 | +} |
| 103 | +.mw-codechallenge-popup { |
| 104 | + display: none; |
| 105 | + position: absolute; |
| 106 | + left: 354px; |
| 107 | + top: 50%; |
| 108 | + margin-top: -22px; |
| 109 | + width: 354px; |
| 110 | + padding: 1.25em; |
| 111 | + border: solid 3px #3263b6; |
| 112 | + -webkit-box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5); |
| 113 | + -moz-box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5); |
| 114 | + box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5); |
| 115 | + background-color: white; |
| 116 | + cursor: default; |
| 117 | +} |
| 118 | +.mw-codechallenge-popup-callout { |
| 119 | + position: absolute; |
| 120 | + left: -16px; |
| 121 | + top: 3px; |
| 122 | + width: 16px; |
| 123 | + height: 32px; |
| 124 | + /* @embed */ |
| 125 | + background-image: url(images/callout.png); |
| 126 | + background-position: left top; |
| 127 | + background-repeat: no-repeat; |
| 128 | +} |
| 129 | +.mw-codechallenge-popup-buttons { |
| 130 | + margin-top: 1em; |
| 131 | + margin-bottom: -0.5em; |
| 132 | + text-align: right; |
| 133 | +} |
| 134 | + |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | + |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/contest.special.welcome.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 143 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.ui.timepicker.js |
— | — | @@ -0,0 +1,1276 @@ |
| 2 | +/* |
| 3 | +* jQuery timepicker addon |
| 4 | +* By: Trent Richardson [http://trentrichardson.com] |
| 5 | +* Version 0.9.7 |
| 6 | +* Last Modified: 10/02/2011 |
| 7 | +* |
| 8 | +* Copyright 2011 Trent Richardson |
| 9 | +* Dual licensed under the MIT and GPL licenses. |
| 10 | +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt |
| 11 | +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt |
| 12 | +* |
| 13 | +* HERES THE CSS: |
| 14 | +* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } |
| 15 | +* .ui-timepicker-div dl { text-align: left; } |
| 16 | +* .ui-timepicker-div dl dt { height: 25px; } |
| 17 | +* .ui-timepicker-div dl dd { margin: -25px 10px 10px 65px; } |
| 18 | +* .ui-timepicker-div td { font-size: 90%; } |
| 19 | +* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } |
| 20 | +*/ |
| 21 | + |
| 22 | +(function($) { |
| 23 | + |
| 24 | +$.extend($.ui, { timepicker: { version: "0.9.7" } }); |
| 25 | + |
| 26 | +/* Time picker manager. |
| 27 | + Use the singleton instance of this class, $.timepicker, to interact with the time picker. |
| 28 | + Settings for (groups of) time pickers are maintained in an instance object, |
| 29 | + allowing multiple different settings on the same page. */ |
| 30 | + |
| 31 | +function Timepicker() { |
| 32 | + this.regional = []; // Available regional settings, indexed by language code |
| 33 | + this.regional[''] = { // Default regional settings |
| 34 | + currentText: 'Now', |
| 35 | + closeText: 'Done', |
| 36 | + ampm: false, |
| 37 | + amNames: ['AM', 'A'], |
| 38 | + pmNames: ['PM', 'P'], |
| 39 | + timeFormat: 'hh:mm tt', |
| 40 | + timeSuffix: '', |
| 41 | + timeOnlyTitle: 'Choose Time', |
| 42 | + timeText: 'Time', |
| 43 | + hourText: 'Hour', |
| 44 | + minuteText: 'Minute', |
| 45 | + secondText: 'Second', |
| 46 | + millisecText: 'Millisecond', |
| 47 | + timezoneText: 'Time Zone' |
| 48 | + }; |
| 49 | + this._defaults = { // Global defaults for all the datetime picker instances |
| 50 | + showButtonPanel: true, |
| 51 | + timeOnly: false, |
| 52 | + showHour: true, |
| 53 | + showMinute: true, |
| 54 | + showSecond: false, |
| 55 | + showMillisec: false, |
| 56 | + showTimezone: false, |
| 57 | + showTime: true, |
| 58 | + stepHour: 0.05, |
| 59 | + stepMinute: 0.05, |
| 60 | + stepSecond: 0.05, |
| 61 | + stepMillisec: 0.5, |
| 62 | + hour: 0, |
| 63 | + minute: 0, |
| 64 | + second: 0, |
| 65 | + millisec: 0, |
| 66 | + timezone: '+0000', |
| 67 | + hourMin: 0, |
| 68 | + minuteMin: 0, |
| 69 | + secondMin: 0, |
| 70 | + millisecMin: 0, |
| 71 | + hourMax: 23, |
| 72 | + minuteMax: 59, |
| 73 | + secondMax: 59, |
| 74 | + millisecMax: 999, |
| 75 | + minDateTime: null, |
| 76 | + maxDateTime: null, |
| 77 | + onSelect: null, |
| 78 | + hourGrid: 0, |
| 79 | + minuteGrid: 0, |
| 80 | + secondGrid: 0, |
| 81 | + millisecGrid: 0, |
| 82 | + alwaysSetTime: true, |
| 83 | + separator: ' ', |
| 84 | + altFieldTimeOnly: true, |
| 85 | + showTimepicker: true, |
| 86 | + timezoneIso8609: false, |
| 87 | + timezoneList: null |
| 88 | + }; |
| 89 | + $.extend(this._defaults, this.regional['']); |
| 90 | +} |
| 91 | + |
| 92 | +$.extend(Timepicker.prototype, { |
| 93 | + $input: null, |
| 94 | + $altInput: null, |
| 95 | + $timeObj: null, |
| 96 | + inst: null, |
| 97 | + hour_slider: null, |
| 98 | + minute_slider: null, |
| 99 | + second_slider: null, |
| 100 | + millisec_slider: null, |
| 101 | + timezone_select: null, |
| 102 | + hour: 0, |
| 103 | + minute: 0, |
| 104 | + second: 0, |
| 105 | + millisec: 0, |
| 106 | + timezone: '+0000', |
| 107 | + hourMinOriginal: null, |
| 108 | + minuteMinOriginal: null, |
| 109 | + secondMinOriginal: null, |
| 110 | + millisecMinOriginal: null, |
| 111 | + hourMaxOriginal: null, |
| 112 | + minuteMaxOriginal: null, |
| 113 | + secondMaxOriginal: null, |
| 114 | + millisecMaxOriginal: null, |
| 115 | + ampm: '', |
| 116 | + formattedDate: '', |
| 117 | + formattedTime: '', |
| 118 | + formattedDateTime: '', |
| 119 | + timezoneList: null, |
| 120 | + |
| 121 | + /* Override the default settings for all instances of the time picker. |
| 122 | + @param settings object - the new settings to use as defaults (anonymous object) |
| 123 | + @return the manager object */ |
| 124 | + setDefaults: function(settings) { |
| 125 | + extendRemove(this._defaults, settings || {}); |
| 126 | + return this; |
| 127 | + }, |
| 128 | + |
| 129 | + //######################################################################## |
| 130 | + // Create a new Timepicker instance |
| 131 | + //######################################################################## |
| 132 | + _newInst: function($input, o) { |
| 133 | + var tp_inst = new Timepicker(), |
| 134 | + inlineSettings = {}; |
| 135 | + |
| 136 | + for (var attrName in this._defaults) { |
| 137 | + var attrValue = $input.attr('time:' + attrName); |
| 138 | + if (attrValue) { |
| 139 | + try { |
| 140 | + inlineSettings[attrName] = eval(attrValue); |
| 141 | + } catch (err) { |
| 142 | + inlineSettings[attrName] = attrValue; |
| 143 | + } |
| 144 | + } |
| 145 | + } |
| 146 | + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, { |
| 147 | + beforeShow: function(input, dp_inst) { |
| 148 | + if ($.isFunction(o.beforeShow)) |
| 149 | + o.beforeShow(input, dp_inst, tp_inst); |
| 150 | + }, |
| 151 | + onChangeMonthYear: function(year, month, dp_inst) { |
| 152 | + // Update the time as well : this prevents the time from disappearing from the $input field. |
| 153 | + tp_inst._updateDateTime(dp_inst); |
| 154 | + if ($.isFunction(o.onChangeMonthYear)) |
| 155 | + o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); |
| 156 | + }, |
| 157 | + onClose: function(dateText, dp_inst) { |
| 158 | + if (tp_inst.timeDefined === true && $input.val() != '') |
| 159 | + tp_inst._updateDateTime(dp_inst); |
| 160 | + if ($.isFunction(o.onClose)) |
| 161 | + o.onClose.call($input[0], dateText, dp_inst, tp_inst); |
| 162 | + }, |
| 163 | + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); |
| 164 | + }); |
| 165 | + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase() }); |
| 166 | + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase() }); |
| 167 | + |
| 168 | + if (tp_inst._defaults.timezoneList === null) { |
| 169 | + var timezoneList = []; |
| 170 | + for (var i = -11; i <= 12; i++) |
| 171 | + timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00'); |
| 172 | + if (tp_inst._defaults.timezoneIso8609) |
| 173 | + timezoneList = $.map(timezoneList, function(val) { |
| 174 | + return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3)); |
| 175 | + }); |
| 176 | + tp_inst._defaults.timezoneList = timezoneList; |
| 177 | + } |
| 178 | + |
| 179 | + tp_inst.hour = tp_inst._defaults.hour; |
| 180 | + tp_inst.minute = tp_inst._defaults.minute; |
| 181 | + tp_inst.second = tp_inst._defaults.second; |
| 182 | + tp_inst.millisec = tp_inst._defaults.millisec; |
| 183 | + tp_inst.ampm = ''; |
| 184 | + tp_inst.$input = $input; |
| 185 | + |
| 186 | + if (o.altField) |
| 187 | + tp_inst.$altInput = $(o.altField) |
| 188 | + .css({ cursor: 'pointer' }) |
| 189 | + .focus(function(){ $input.trigger("focus"); }); |
| 190 | + |
| 191 | + if(tp_inst._defaults.minDate==0 || tp_inst._defaults.minDateTime==0) |
| 192 | + { |
| 193 | + tp_inst._defaults.minDate=new Date(); |
| 194 | + } |
| 195 | + if(tp_inst._defaults.maxDate==0 || tp_inst._defaults.maxDateTime==0) |
| 196 | + { |
| 197 | + tp_inst._defaults.maxDate=new Date(); |
| 198 | + } |
| 199 | + |
| 200 | + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. |
| 201 | + if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) |
| 202 | + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); |
| 203 | + if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) |
| 204 | + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); |
| 205 | + if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) |
| 206 | + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); |
| 207 | + if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) |
| 208 | + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); |
| 209 | + return tp_inst; |
| 210 | + }, |
| 211 | + |
| 212 | + //######################################################################## |
| 213 | + // add our sliders to the calendar |
| 214 | + //######################################################################## |
| 215 | + _addTimePicker: function(dp_inst) { |
| 216 | + var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? |
| 217 | + this.$input.val() + ' ' + this.$altInput.val() : |
| 218 | + this.$input.val(); |
| 219 | + |
| 220 | + this.timeDefined = this._parseTime(currDT); |
| 221 | + this._limitMinMaxDateTime(dp_inst, false); |
| 222 | + this._injectTimePicker(); |
| 223 | + }, |
| 224 | + |
| 225 | + //######################################################################## |
| 226 | + // parse the time string from input value or _setTime |
| 227 | + //######################################################################## |
| 228 | + _parseTime: function(timeString, withDate) { |
| 229 | + var regstr = this._defaults.timeFormat.toString() |
| 230 | + .replace(/h{1,2}/ig, '(\\d?\\d)') |
| 231 | + .replace(/m{1,2}/ig, '(\\d?\\d)') |
| 232 | + .replace(/s{1,2}/ig, '(\\d?\\d)') |
| 233 | + .replace(/l{1}/ig, '(\\d?\\d?\\d)') |
| 234 | + .replace(/t{1,2}/ig, this._getPatternAmpm()) |
| 235 | + .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?') |
| 236 | + .replace(/\s/g, '\\s?') + this._defaults.timeSuffix + '$', |
| 237 | + order = this._getFormatPositions(), |
| 238 | + ampm = '', |
| 239 | + treg; |
| 240 | + |
| 241 | + if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]); |
| 242 | + |
| 243 | + if (withDate || !this._defaults.timeOnly) { |
| 244 | + // the time should come after x number of characters and a space. |
| 245 | + // x = at least the length of text specified by the date format |
| 246 | + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); |
| 247 | + // escape special regex characters in the seperator |
| 248 | + var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); |
| 249 | + regstr = '.{' + dp_dateFormat.length + ',}' + this._defaults.separator.replace(specials, "\\$&") + regstr; |
| 250 | + } |
| 251 | + |
| 252 | + treg = timeString.match(new RegExp(regstr, 'i')); |
| 253 | + |
| 254 | + if (treg) { |
| 255 | + if (order.t !== -1) { |
| 256 | + if (treg[order.t] === undefined || treg[order.t].length === 0) { |
| 257 | + ampm = ''; |
| 258 | + this.ampm = ''; |
| 259 | + } else { |
| 260 | + ampm = $.inArray(treg[order.t].toUpperCase(), this.amNames) !== -1 ? 'AM' : 'PM'; |
| 261 | + this.ampm = this._defaults[ampm == 'AM' ? 'amNames' : 'pmNames'][0]; |
| 262 | + } |
| 263 | + } |
| 264 | + |
| 265 | + if (order.h !== -1) { |
| 266 | + if (ampm == 'AM' && treg[order.h] == '12') |
| 267 | + this.hour = 0; // 12am = 0 hour |
| 268 | + else if (ampm == 'PM' && treg[order.h] != '12') |
| 269 | + this.hour = (parseFloat(treg[order.h]) + 12).toFixed(0); // 12pm = 12 hour, any other pm = hour + 12 |
| 270 | + else this.hour = Number(treg[order.h]); |
| 271 | + } |
| 272 | + |
| 273 | + if (order.m !== -1) this.minute = Number(treg[order.m]); |
| 274 | + if (order.s !== -1) this.second = Number(treg[order.s]); |
| 275 | + if (order.l !== -1) this.millisec = Number(treg[order.l]); |
| 276 | + if (order.z !== -1 && treg[order.z] !== undefined) { |
| 277 | + var tz = treg[order.z].toUpperCase(); |
| 278 | + switch (tz.length) { |
| 279 | + case 1: // Z |
| 280 | + tz = this._defaults.timezoneIso8609 ? 'Z' : '+0000'; |
| 281 | + break; |
| 282 | + case 5: // +hhmm |
| 283 | + if (this._defaults.timezoneIso8609) |
| 284 | + tz = tz.substring(1) == '0000' |
| 285 | + ? 'Z' |
| 286 | + : tz.substring(0, 3) + ':' + tz.substring(3); |
| 287 | + break; |
| 288 | + case 6: // +hh:mm |
| 289 | + if (!this._defaults.timezoneIso8609) |
| 290 | + tz = tz == 'Z' || tz.substring(1) == '00:00' |
| 291 | + ? '+0000' |
| 292 | + : tz.replace(/:/, ''); |
| 293 | + else if (tz.substring(1) == '00:00') |
| 294 | + tz = 'Z'; |
| 295 | + break; |
| 296 | + } |
| 297 | + this.timezone = tz; |
| 298 | + } |
| 299 | + |
| 300 | + return true; |
| 301 | + |
| 302 | + } |
| 303 | + return false; |
| 304 | + }, |
| 305 | + |
| 306 | + //######################################################################## |
| 307 | + // pattern for standard and localized AM/PM markers |
| 308 | + //######################################################################## |
| 309 | + _getPatternAmpm: function() { |
| 310 | + var markers = []; |
| 311 | + o = this._defaults; |
| 312 | + if (o.amNames) |
| 313 | + $.merge(markers, o.amNames); |
| 314 | + if (o.pmNames) |
| 315 | + $.merge(markers, o.pmNames); |
| 316 | + markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&') }); |
| 317 | + return '(' + markers.join('|') + ')?'; |
| 318 | + }, |
| 319 | + |
| 320 | + //######################################################################## |
| 321 | + // figure out position of time elements.. cause js cant do named captures |
| 322 | + //######################################################################## |
| 323 | + _getFormatPositions: function() { |
| 324 | + var finds = this._defaults.timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g), |
| 325 | + orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 }; |
| 326 | + |
| 327 | + if (finds) |
| 328 | + for (var i = 0; i < finds.length; i++) |
| 329 | + if (orders[finds[i].toString().charAt(0)] == -1) |
| 330 | + orders[finds[i].toString().charAt(0)] = i + 1; |
| 331 | + |
| 332 | + return orders; |
| 333 | + }, |
| 334 | + |
| 335 | + //######################################################################## |
| 336 | + // generate and inject html for timepicker into ui datepicker |
| 337 | + //######################################################################## |
| 338 | + _injectTimePicker: function() { |
| 339 | + var $dp = this.inst.dpDiv, |
| 340 | + o = this._defaults, |
| 341 | + tp_inst = this, |
| 342 | + // Added by Peter Medeiros: |
| 343 | + // - Figure out what the hour/minute/second max should be based on the step values. |
| 344 | + // - Example: if stepMinute is 15, then minMax is 45. |
| 345 | + hourMax = (o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)).toFixed(0), |
| 346 | + minMax = (o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)).toFixed(0), |
| 347 | + secMax = (o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)).toFixed(0), |
| 348 | + millisecMax = (o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)).toFixed(0), |
| 349 | + dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, ''); |
| 350 | + |
| 351 | + // Prevent displaying twice |
| 352 | + //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) { |
| 353 | + if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) { |
| 354 | + var noDisplay = ' style="display:none;"', |
| 355 | + html = '<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' + |
| 356 | + '<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' + |
| 357 | + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + |
| 358 | + '<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' + |
| 359 | + ((o.showTime) ? '' : noDisplay) + '></dd>' + |
| 360 | + '<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' + |
| 361 | + ((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>', |
| 362 | + hourGridSize = 0, |
| 363 | + minuteGridSize = 0, |
| 364 | + secondGridSize = 0, |
| 365 | + millisecGridSize = 0, |
| 366 | + size; |
| 367 | + |
| 368 | + // Hours |
| 369 | + if (o.showHour && o.hourGrid > 0) { |
| 370 | + html += '<dd class="ui_tpicker_hour">' + |
| 371 | + '<div id="ui_tpicker_hour_' + dp_id + '"' + ((o.showHour) ? '' : noDisplay) + '></div>' + |
| 372 | + '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; |
| 373 | + |
| 374 | + for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) { |
| 375 | + hourGridSize++; |
| 376 | + var tmph = (o.ampm && h > 12) ? h-12 : h; |
| 377 | + if (tmph < 10) tmph = '0' + tmph; |
| 378 | + if (o.ampm) { |
| 379 | + if (h == 0) tmph = 12 +'a'; |
| 380 | + else if (h < 12) tmph += 'a'; |
| 381 | + else tmph += 'p'; |
| 382 | + } |
| 383 | + html += '<td>' + tmph + '</td>'; |
| 384 | + } |
| 385 | + |
| 386 | + html += '</tr></table></div>' + |
| 387 | + '</dd>'; |
| 388 | + } else html += '<dd class="ui_tpicker_hour" id="ui_tpicker_hour_' + dp_id + '"' + |
| 389 | + ((o.showHour) ? '' : noDisplay) + '></dd>'; |
| 390 | + |
| 391 | + html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' + |
| 392 | + ((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>'; |
| 393 | + |
| 394 | + // Minutes |
| 395 | + if (o.showMinute && o.minuteGrid > 0) { |
| 396 | + html += '<dd class="ui_tpicker_minute ui_tpicker_minute_' + o.minuteGrid + '">' + |
| 397 | + '<div id="ui_tpicker_minute_' + dp_id + '"' + |
| 398 | + ((o.showMinute) ? '' : noDisplay) + '></div>' + |
| 399 | + '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; |
| 400 | + |
| 401 | + for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) { |
| 402 | + minuteGridSize++; |
| 403 | + html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>'; |
| 404 | + } |
| 405 | + |
| 406 | + html += '</tr></table></div>' + |
| 407 | + '</dd>'; |
| 408 | + } else html += '<dd class="ui_tpicker_minute" id="ui_tpicker_minute_' + dp_id + '"' + |
| 409 | + ((o.showMinute) ? '' : noDisplay) + '></dd>'; |
| 410 | + |
| 411 | + // Seconds |
| 412 | + html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' + |
| 413 | + ((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>'; |
| 414 | + |
| 415 | + if (o.showSecond && o.secondGrid > 0) { |
| 416 | + html += '<dd class="ui_tpicker_second ui_tpicker_second_' + o.secondGrid + '">' + |
| 417 | + '<div id="ui_tpicker_second_' + dp_id + '"' + |
| 418 | + ((o.showSecond) ? '' : noDisplay) + '></div>' + |
| 419 | + '<div style="padding-left: 1px"><table><tr>'; |
| 420 | + |
| 421 | + for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) { |
| 422 | + secondGridSize++; |
| 423 | + html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>'; |
| 424 | + } |
| 425 | + |
| 426 | + html += '</tr></table></div>' + |
| 427 | + '</dd>'; |
| 428 | + } else html += '<dd class="ui_tpicker_second" id="ui_tpicker_second_' + dp_id + '"' + |
| 429 | + ((o.showSecond) ? '' : noDisplay) + '></dd>'; |
| 430 | + |
| 431 | + // Milliseconds |
| 432 | + html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' + |
| 433 | + ((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>'; |
| 434 | + |
| 435 | + if (o.showMillisec && o.millisecGrid > 0) { |
| 436 | + html += '<dd class="ui_tpicker_millisec ui_tpicker_millisec_' + o.millisecGrid + '">' + |
| 437 | + '<div id="ui_tpicker_millisec_' + dp_id + '"' + |
| 438 | + ((o.showMillisec) ? '' : noDisplay) + '></div>' + |
| 439 | + '<div style="padding-left: 1px"><table><tr>'; |
| 440 | + |
| 441 | + for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) { |
| 442 | + millisecGridSize++; |
| 443 | + html += '<td>' + ((l < 10) ? '0' : '') + s + '</td>'; |
| 444 | + } |
| 445 | + |
| 446 | + html += '</tr></table></div>' + |
| 447 | + '</dd>'; |
| 448 | + } else html += '<dd class="ui_tpicker_millisec" id="ui_tpicker_millisec_' + dp_id + '"' + |
| 449 | + ((o.showMillisec) ? '' : noDisplay) + '></dd>'; |
| 450 | + |
| 451 | + // Timezone |
| 452 | + html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' + |
| 453 | + ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>'; |
| 454 | + html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"' + |
| 455 | + ((o.showTimezone) ? '' : noDisplay) + '></dd>'; |
| 456 | + |
| 457 | + html += '</dl></div>'; |
| 458 | + $tp = $(html); |
| 459 | + |
| 460 | + // if we only want time picker... |
| 461 | + if (o.timeOnly === true) { |
| 462 | + $tp.prepend( |
| 463 | + '<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + |
| 464 | + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + |
| 465 | + '</div>'); |
| 466 | + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); |
| 467 | + } |
| 468 | + |
| 469 | + this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({ |
| 470 | + orientation: "horizontal", |
| 471 | + value: this.hour, |
| 472 | + min: o.hourMin, |
| 473 | + max: hourMax, |
| 474 | + step: o.stepHour, |
| 475 | + slide: function(event, ui) { |
| 476 | + tp_inst.hour_slider.slider( "option", "value", ui.value); |
| 477 | + tp_inst._onTimeChange(); |
| 478 | + } |
| 479 | + }); |
| 480 | + |
| 481 | + // Updated by Peter Medeiros: |
| 482 | + // - Pass in Event and UI instance into slide function |
| 483 | + this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({ |
| 484 | + orientation: "horizontal", |
| 485 | + value: this.minute, |
| 486 | + min: o.minuteMin, |
| 487 | + max: minMax, |
| 488 | + step: o.stepMinute, |
| 489 | + slide: function(event, ui) { |
| 490 | + // update the global minute slider instance value with the current slider value |
| 491 | + tp_inst.minute_slider.slider( "option", "value", ui.value); |
| 492 | + tp_inst._onTimeChange(); |
| 493 | + } |
| 494 | + }); |
| 495 | + |
| 496 | + this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({ |
| 497 | + orientation: "horizontal", |
| 498 | + value: this.second, |
| 499 | + min: o.secondMin, |
| 500 | + max: secMax, |
| 501 | + step: o.stepSecond, |
| 502 | + slide: function(event, ui) { |
| 503 | + tp_inst.second_slider.slider( "option", "value", ui.value); |
| 504 | + tp_inst._onTimeChange(); |
| 505 | + } |
| 506 | + }); |
| 507 | + |
| 508 | + this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({ |
| 509 | + orientation: "horizontal", |
| 510 | + value: this.millisec, |
| 511 | + min: o.millisecMin, |
| 512 | + max: millisecMax, |
| 513 | + step: o.stepMillisec, |
| 514 | + slide: function(event, ui) { |
| 515 | + tp_inst.millisec_slider.slider( "option", "value", ui.value); |
| 516 | + tp_inst._onTimeChange(); |
| 517 | + } |
| 518 | + }); |
| 519 | + |
| 520 | + this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select"); |
| 521 | + $.fn.append.apply(this.timezone_select, |
| 522 | + $.map(o.timezoneList, function(val, idx) { |
| 523 | + return $("<option />") |
| 524 | + .val(typeof val == "object" ? val.value : val) |
| 525 | + .text(typeof val == "object" ? val.label : val); |
| 526 | + }) |
| 527 | + ); |
| 528 | + this.timezone_select.val((typeof this.timezone != "undefined" && this.timezone != null && this.timezone != "") ? this.timezone : o.timezone); |
| 529 | + this.timezone_select.change(function() { |
| 530 | + tp_inst._onTimeChange(); |
| 531 | + }); |
| 532 | + |
| 533 | + // Add grid functionality |
| 534 | + if (o.showHour && o.hourGrid > 0) { |
| 535 | + size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin); |
| 536 | + |
| 537 | + $tp.find(".ui_tpicker_hour table").css({ |
| 538 | + width: size + "%", |
| 539 | + marginLeft: (size / (-2 * hourGridSize)) + "%", |
| 540 | + borderCollapse: 'collapse' |
| 541 | + }).find("td").each( function(index) { |
| 542 | + $(this).click(function() { |
| 543 | + var h = $(this).html(); |
| 544 | + if(o.ampm) { |
| 545 | + var ap = h.substring(2).toLowerCase(), |
| 546 | + aph = parseInt(h.substring(0,2), 10); |
| 547 | + if (ap == 'a') { |
| 548 | + if (aph == 12) h = 0; |
| 549 | + else h = aph; |
| 550 | + } else if (aph == 12) h = 12; |
| 551 | + else h = aph + 12; |
| 552 | + } |
| 553 | + tp_inst.hour_slider.slider("option", "value", h); |
| 554 | + tp_inst._onTimeChange(); |
| 555 | + tp_inst._onSelectHandler(); |
| 556 | + }).css({ |
| 557 | + cursor: 'pointer', |
| 558 | + width: (100 / hourGridSize) + '%', |
| 559 | + textAlign: 'center', |
| 560 | + overflow: 'hidden' |
| 561 | + }); |
| 562 | + }); |
| 563 | + } |
| 564 | + |
| 565 | + if (o.showMinute && o.minuteGrid > 0) { |
| 566 | + size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin); |
| 567 | + $tp.find(".ui_tpicker_minute table").css({ |
| 568 | + width: size + "%", |
| 569 | + marginLeft: (size / (-2 * minuteGridSize)) + "%", |
| 570 | + borderCollapse: 'collapse' |
| 571 | + }).find("td").each(function(index) { |
| 572 | + $(this).click(function() { |
| 573 | + tp_inst.minute_slider.slider("option", "value", $(this).html()); |
| 574 | + tp_inst._onTimeChange(); |
| 575 | + tp_inst._onSelectHandler(); |
| 576 | + }).css({ |
| 577 | + cursor: 'pointer', |
| 578 | + width: (100 / minuteGridSize) + '%', |
| 579 | + textAlign: 'center', |
| 580 | + overflow: 'hidden' |
| 581 | + }); |
| 582 | + }); |
| 583 | + } |
| 584 | + |
| 585 | + if (o.showSecond && o.secondGrid > 0) { |
| 586 | + $tp.find(".ui_tpicker_second table").css({ |
| 587 | + width: size + "%", |
| 588 | + marginLeft: (size / (-2 * secondGridSize)) + "%", |
| 589 | + borderCollapse: 'collapse' |
| 590 | + }).find("td").each(function(index) { |
| 591 | + $(this).click(function() { |
| 592 | + tp_inst.second_slider.slider("option", "value", $(this).html()); |
| 593 | + tp_inst._onTimeChange(); |
| 594 | + tp_inst._onSelectHandler(); |
| 595 | + }).css({ |
| 596 | + cursor: 'pointer', |
| 597 | + width: (100 / secondGridSize) + '%', |
| 598 | + textAlign: 'center', |
| 599 | + overflow: 'hidden' |
| 600 | + }); |
| 601 | + }); |
| 602 | + } |
| 603 | + |
| 604 | + if (o.showMillisec && o.millisecGrid > 0) { |
| 605 | + $tp.find(".ui_tpicker_millisec table").css({ |
| 606 | + width: size + "%", |
| 607 | + marginLeft: (size / (-2 * millisecGridSize)) + "%", |
| 608 | + borderCollapse: 'collapse' |
| 609 | + }).find("td").each(function(index) { |
| 610 | + $(this).click(function() { |
| 611 | + tp_inst.millisec_slider.slider("option", "value", $(this).html()); |
| 612 | + tp_inst._onTimeChange(); |
| 613 | + tp_inst._onSelectHandler(); |
| 614 | + }).css({ |
| 615 | + cursor: 'pointer', |
| 616 | + width: (100 / millisecGridSize) + '%', |
| 617 | + textAlign: 'center', |
| 618 | + overflow: 'hidden' |
| 619 | + }); |
| 620 | + }); |
| 621 | + } |
| 622 | + |
| 623 | + var $buttonPanel = $dp.find('.ui-datepicker-buttonpane'); |
| 624 | + if ($buttonPanel.length) $buttonPanel.before($tp); |
| 625 | + else $dp.append($tp); |
| 626 | + |
| 627 | + this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id); |
| 628 | + |
| 629 | + if (this.inst !== null) { |
| 630 | + var timeDefined = this.timeDefined; |
| 631 | + this._onTimeChange(); |
| 632 | + this.timeDefined = timeDefined; |
| 633 | + } |
| 634 | + |
| 635 | + //Emulate datepicker onSelect behavior. Call on slidestop. |
| 636 | + var onSelectDelegate = function() { |
| 637 | + tp_inst._onSelectHandler(); |
| 638 | + }; |
| 639 | + this.hour_slider.bind('slidestop',onSelectDelegate); |
| 640 | + this.minute_slider.bind('slidestop',onSelectDelegate); |
| 641 | + this.second_slider.bind('slidestop',onSelectDelegate); |
| 642 | + this.millisec_slider.bind('slidestop',onSelectDelegate); |
| 643 | + } |
| 644 | + }, |
| 645 | + |
| 646 | + //######################################################################## |
| 647 | + // This function tries to limit the ability to go outside the |
| 648 | + // min/max date range |
| 649 | + //######################################################################## |
| 650 | + _limitMinMaxDateTime: function(dp_inst, adjustSliders){ |
| 651 | + var o = this._defaults, |
| 652 | + dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay); |
| 653 | + |
| 654 | + if(!this._defaults.showTimepicker) return; // No time so nothing to check here |
| 655 | + |
| 656 | + if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){ |
| 657 | + var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'), |
| 658 | + minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0); |
| 659 | + |
| 660 | + if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){ |
| 661 | + this.hourMinOriginal = o.hourMin; |
| 662 | + this.minuteMinOriginal = o.minuteMin; |
| 663 | + this.secondMinOriginal = o.secondMin; |
| 664 | + this.millisecMinOriginal = o.millisecMin; |
| 665 | + } |
| 666 | + |
| 667 | + if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) { |
| 668 | + this._defaults.hourMin = minDateTime.getHours(); |
| 669 | + if (this.hour <= this._defaults.hourMin) { |
| 670 | + this.hour = this._defaults.hourMin; |
| 671 | + this._defaults.minuteMin = minDateTime.getMinutes(); |
| 672 | + if (this.minute <= this._defaults.minuteMin) { |
| 673 | + this.minute = this._defaults.minuteMin; |
| 674 | + this._defaults.secondMin = minDateTime.getSeconds(); |
| 675 | + } else if (this.second <= this._defaults.secondMin){ |
| 676 | + this.second = this._defaults.secondMin; |
| 677 | + this._defaults.millisecMin = minDateTime.getMilliseconds(); |
| 678 | + } else { |
| 679 | + if(this.millisec < this._defaults.millisecMin) |
| 680 | + this.millisec = this._defaults.millisecMin; |
| 681 | + this._defaults.millisecMin = this.millisecMinOriginal; |
| 682 | + } |
| 683 | + } else { |
| 684 | + this._defaults.minuteMin = this.minuteMinOriginal; |
| 685 | + this._defaults.secondMin = this.secondMinOriginal; |
| 686 | + this._defaults.millisecMin = this.millisecMinOriginal; |
| 687 | + } |
| 688 | + }else{ |
| 689 | + this._defaults.hourMin = this.hourMinOriginal; |
| 690 | + this._defaults.minuteMin = this.minuteMinOriginal; |
| 691 | + this._defaults.secondMin = this.secondMinOriginal; |
| 692 | + this._defaults.millisecMin = this.millisecMinOriginal; |
| 693 | + } |
| 694 | + } |
| 695 | + |
| 696 | + if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){ |
| 697 | + var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'), |
| 698 | + maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0); |
| 699 | + |
| 700 | + if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){ |
| 701 | + this.hourMaxOriginal = o.hourMax; |
| 702 | + this.minuteMaxOriginal = o.minuteMax; |
| 703 | + this.secondMaxOriginal = o.secondMax; |
| 704 | + this.millisecMaxOriginal = o.millisecMax; |
| 705 | + } |
| 706 | + |
| 707 | + if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){ |
| 708 | + this._defaults.hourMax = maxDateTime.getHours(); |
| 709 | + if (this.hour >= this._defaults.hourMax) { |
| 710 | + this.hour = this._defaults.hourMax; |
| 711 | + this._defaults.minuteMax = maxDateTime.getMinutes(); |
| 712 | + if (this.minute >= this._defaults.minuteMax) { |
| 713 | + this.minute = this._defaults.minuteMax; |
| 714 | + this._defaults.secondMax = maxDateTime.getSeconds(); |
| 715 | + } else if (this.second >= this._defaults.secondMax) { |
| 716 | + this.second = this._defaults.secondMax; |
| 717 | + this._defaults.millisecMax = maxDateTime.getMilliseconds(); |
| 718 | + } else { |
| 719 | + if(this.millisec > this._defaults.millisecMax) this.millisec = this._defaults.millisecMax; |
| 720 | + this._defaults.millisecMax = this.millisecMaxOriginal; |
| 721 | + } |
| 722 | + } else { |
| 723 | + this._defaults.minuteMax = this.minuteMaxOriginal; |
| 724 | + this._defaults.secondMax = this.secondMaxOriginal; |
| 725 | + this._defaults.millisecMax = this.millisecMaxOriginal; |
| 726 | + } |
| 727 | + }else{ |
| 728 | + this._defaults.hourMax = this.hourMaxOriginal; |
| 729 | + this._defaults.minuteMax = this.minuteMaxOriginal; |
| 730 | + this._defaults.secondMax = this.secondMaxOriginal; |
| 731 | + this._defaults.millisecMax = this.millisecMaxOriginal; |
| 732 | + } |
| 733 | + } |
| 734 | + |
| 735 | + if(adjustSliders !== undefined && adjustSliders === true){ |
| 736 | + var hourMax = (this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)).toFixed(0), |
| 737 | + minMax = (this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)).toFixed(0), |
| 738 | + secMax = (this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)).toFixed(0), |
| 739 | + millisecMax = (this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)).toFixed(0); |
| 740 | + |
| 741 | + if(this.hour_slider) |
| 742 | + this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour); |
| 743 | + if(this.minute_slider) |
| 744 | + this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute); |
| 745 | + if(this.second_slider) |
| 746 | + this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second); |
| 747 | + if(this.millisec_slider) |
| 748 | + this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec); |
| 749 | + } |
| 750 | + |
| 751 | + }, |
| 752 | + |
| 753 | + |
| 754 | + //######################################################################## |
| 755 | + // when a slider moves, set the internal time... |
| 756 | + // on time change is also called when the time is updated in the text field |
| 757 | + //######################################################################## |
| 758 | + _onTimeChange: function() { |
| 759 | + var hour = (this.hour_slider) ? this.hour_slider.slider('value') : false, |
| 760 | + minute = (this.minute_slider) ? this.minute_slider.slider('value') : false, |
| 761 | + second = (this.second_slider) ? this.second_slider.slider('value') : false, |
| 762 | + millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false, |
| 763 | + timezone = (this.timezone_select) ? this.timezone_select.val() : false, |
| 764 | + o = this._defaults; |
| 765 | + |
| 766 | + if (typeof(hour) == 'object') hour = false; |
| 767 | + if (typeof(minute) == 'object') minute = false; |
| 768 | + if (typeof(second) == 'object') second = false; |
| 769 | + if (typeof(millisec) == 'object') millisec = false; |
| 770 | + if (typeof(timezone) == 'object') timezone = false; |
| 771 | + |
| 772 | + if (hour !== false) hour = parseInt(hour,10); |
| 773 | + if (minute !== false) minute = parseInt(minute,10); |
| 774 | + if (second !== false) second = parseInt(second,10); |
| 775 | + if (millisec !== false) millisec = parseInt(millisec,10); |
| 776 | + |
| 777 | + var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0]; |
| 778 | + |
| 779 | + // If the update was done in the input field, the input field should not be updated. |
| 780 | + // If the update was done using the sliders, update the input field. |
| 781 | + var hasChanged = (hour != this.hour || minute != this.minute |
| 782 | + || second != this.second || millisec != this.millisec |
| 783 | + || (this.ampm.length > 0 |
| 784 | + && (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) |
| 785 | + || timezone != this.timezone); |
| 786 | + |
| 787 | + if (hasChanged) { |
| 788 | + |
| 789 | + if (hour !== false)this.hour = hour; |
| 790 | + if (minute !== false) this.minute = minute; |
| 791 | + if (second !== false) this.second = second; |
| 792 | + if (millisec !== false) this.millisec = millisec; |
| 793 | + if (timezone !== false) this.timezone = timezone; |
| 794 | + |
| 795 | + if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]); |
| 796 | + |
| 797 | + this._limitMinMaxDateTime(this.inst, true); |
| 798 | + } |
| 799 | + if (o.ampm) this.ampm = ampm; |
| 800 | + |
| 801 | + this._formatTime(); |
| 802 | + if (this.$timeObj) this.$timeObj.text(this.formattedTime + o.timeSuffix); |
| 803 | + this.timeDefined = true; |
| 804 | + if (hasChanged) this._updateDateTime(); |
| 805 | + }, |
| 806 | + |
| 807 | + //######################################################################## |
| 808 | + // call custom onSelect. |
| 809 | + // bind to sliders slidestop, and grid click. |
| 810 | + //######################################################################## |
| 811 | + _onSelectHandler: function() { |
| 812 | + var onSelect = this._defaults.onSelect; |
| 813 | + var inputEl = this.$input ? this.$input[0] : null; |
| 814 | + if (onSelect && inputEl) { |
| 815 | + onSelect.apply(inputEl, [this.formattedDateTime, this]); |
| 816 | + } |
| 817 | + }, |
| 818 | + |
| 819 | + //######################################################################## |
| 820 | + // format the time all pretty... |
| 821 | + //######################################################################## |
| 822 | + _formatTime: function(time, format, ampm) { |
| 823 | + if (ampm == undefined) ampm = this._defaults.ampm; |
| 824 | + time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone }; |
| 825 | + var tmptime = (format || this._defaults.timeFormat).toString(); |
| 826 | + |
| 827 | + var hour = parseInt(time.hour, 10); |
| 828 | + if (ampm) { |
| 829 | + if (!$.inArray(time.ampm.toUpperCase(), this.amNames) !== -1) |
| 830 | + hour = hour % 12; |
| 831 | + if (hour === 0) |
| 832 | + hour = 12; |
| 833 | + } |
| 834 | + tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) { |
| 835 | + switch (match.toLowerCase()) { |
| 836 | + case 'hh': return ('0' + hour).slice(-2); |
| 837 | + case 'h': return hour; |
| 838 | + case 'mm': return ('0' + time.minute).slice(-2); |
| 839 | + case 'm': return time.minute; |
| 840 | + case 'ss': return ('0' + time.second).slice(-2); |
| 841 | + case 's': return time.second; |
| 842 | + case 'l': return ('00' + time.millisec).slice(-3); |
| 843 | + case 'z': return time.timezone; |
| 844 | + case 't': case 'tt': |
| 845 | + if (ampm) { |
| 846 | + var _ampm = time.ampm; |
| 847 | + if (match.length == 1) |
| 848 | + _ampm = _ampm.charAt(0); |
| 849 | + return match.charAt(0) == 'T' ? _ampm.toUpperCase() : _ampm.toLowerCase(); |
| 850 | + } |
| 851 | + return ''; |
| 852 | + } |
| 853 | + }); |
| 854 | + |
| 855 | + if (arguments.length) return tmptime; |
| 856 | + else this.formattedTime = tmptime; |
| 857 | + }, |
| 858 | + |
| 859 | + //######################################################################## |
| 860 | + // update our input with the new date time.. |
| 861 | + //######################################################################## |
| 862 | + _updateDateTime: function(dp_inst) { |
| 863 | + dp_inst = this.inst || dp_inst, |
| 864 | + dt = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay), |
| 865 | + dateFmt = $.datepicker._get(dp_inst, 'dateFormat'), |
| 866 | + formatCfg = $.datepicker._getFormatConfig(dp_inst), |
| 867 | + timeAvailable = dt !== null && this.timeDefined; |
| 868 | + this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg); |
| 869 | + var formattedDateTime = this.formattedDate; |
| 870 | + if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) |
| 871 | + return; |
| 872 | + |
| 873 | + if (this._defaults.timeOnly === true) { |
| 874 | + formattedDateTime = this.formattedTime; |
| 875 | + } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) { |
| 876 | + formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; |
| 877 | + } |
| 878 | + |
| 879 | + this.formattedDateTime = formattedDateTime; |
| 880 | + |
| 881 | + if(!this._defaults.showTimepicker) { |
| 882 | + this.$input.val(this.formattedDate); |
| 883 | + } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) { |
| 884 | + this.$altInput.val(this.formattedTime); |
| 885 | + this.$input.val(this.formattedDate); |
| 886 | + } else if(this.$altInput) { |
| 887 | + this.$altInput.val(formattedDateTime); |
| 888 | + this.$input.val(formattedDateTime); |
| 889 | + } else { |
| 890 | + this.$input.val(formattedDateTime); |
| 891 | + } |
| 892 | + |
| 893 | + this.$input.trigger("change"); |
| 894 | + } |
| 895 | + |
| 896 | +}); |
| 897 | + |
| 898 | +$.fn.extend({ |
| 899 | + //######################################################################## |
| 900 | + // shorthand just to use timepicker.. |
| 901 | + //######################################################################## |
| 902 | + timepicker: function(o) { |
| 903 | + o = o || {}; |
| 904 | + var tmp_args = arguments; |
| 905 | + |
| 906 | + if (typeof o == 'object') tmp_args[0] = $.extend(o, { timeOnly: true }); |
| 907 | + |
| 908 | + return $(this).each(function() { |
| 909 | + $.fn.datetimepicker.apply($(this), tmp_args); |
| 910 | + }); |
| 911 | + }, |
| 912 | + |
| 913 | + //######################################################################## |
| 914 | + // extend timepicker to datepicker |
| 915 | + //######################################################################## |
| 916 | + datetimepicker: function(o) { |
| 917 | + o = o || {}; |
| 918 | + var $input = this, |
| 919 | + tmp_args = arguments; |
| 920 | + |
| 921 | + if (typeof(o) == 'string'){ |
| 922 | + if(o == 'getDate') |
| 923 | + return $.fn.datepicker.apply($(this[0]), tmp_args); |
| 924 | + else |
| 925 | + return this.each(function() { |
| 926 | + var $t = $(this); |
| 927 | + $t.datepicker.apply($t, tmp_args); |
| 928 | + }); |
| 929 | + } |
| 930 | + else |
| 931 | + return this.each(function() { |
| 932 | + var $t = $(this); |
| 933 | + $t.datepicker($.timepicker._newInst($t, o)._defaults); |
| 934 | + }); |
| 935 | + } |
| 936 | +}); |
| 937 | + |
| 938 | +//######################################################################## |
| 939 | +// the bad hack :/ override datepicker so it doesnt close on select |
| 940 | +// inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378 |
| 941 | +//######################################################################## |
| 942 | +$.datepicker._base_selectDate = $.datepicker._selectDate; |
| 943 | +$.datepicker._selectDate = function (id, dateStr) { |
| 944 | + var inst = this._getInst($(id)[0]), |
| 945 | + tp_inst = this._get(inst, 'timepicker'); |
| 946 | + |
| 947 | + if (tp_inst) { |
| 948 | + tp_inst._limitMinMaxDateTime(inst, true); |
| 949 | + inst.inline = inst.stay_open = true; |
| 950 | + //This way the onSelect handler called from calendarpicker get the full dateTime |
| 951 | + this._base_selectDate(id, dateStr); |
| 952 | + inst.inline = inst.stay_open = false; |
| 953 | + this._notifyChange(inst); |
| 954 | + this._updateDatepicker(inst); |
| 955 | + } |
| 956 | + else this._base_selectDate(id, dateStr); |
| 957 | +}; |
| 958 | + |
| 959 | +//############################################################################################# |
| 960 | +// second bad hack :/ override datepicker so it triggers an event when changing the input field |
| 961 | +// and does not redraw the datepicker on every selectDate event |
| 962 | +//############################################################################################# |
| 963 | +$.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker; |
| 964 | +$.datepicker._updateDatepicker = function(inst) { |
| 965 | + |
| 966 | + // don't popup the datepicker if there is another instance already opened |
| 967 | + var input = inst.input[0]; |
| 968 | + if($.datepicker._curInst && |
| 969 | + $.datepicker._curInst != inst && |
| 970 | + $.datepicker._datepickerShowing && |
| 971 | + $.datepicker._lastInput != input) { |
| 972 | + return; |
| 973 | + } |
| 974 | + |
| 975 | + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { |
| 976 | + |
| 977 | + this._base_updateDatepicker(inst); |
| 978 | + |
| 979 | + // Reload the time control when changing something in the input text field. |
| 980 | + var tp_inst = this._get(inst, 'timepicker'); |
| 981 | + if(tp_inst) tp_inst._addTimePicker(inst); |
| 982 | + } |
| 983 | +}; |
| 984 | + |
| 985 | +//####################################################################################### |
| 986 | +// third bad hack :/ override datepicker so it allows spaces and colon in the input field |
| 987 | +//####################################################################################### |
| 988 | +$.datepicker._base_doKeyPress = $.datepicker._doKeyPress; |
| 989 | +$.datepicker._doKeyPress = function(event) { |
| 990 | + var inst = $.datepicker._getInst(event.target), |
| 991 | + tp_inst = $.datepicker._get(inst, 'timepicker'); |
| 992 | + |
| 993 | + if (tp_inst) { |
| 994 | + if ($.datepicker._get(inst, 'constrainInput')) { |
| 995 | + var ampm = tp_inst._defaults.ampm, |
| 996 | + dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')), |
| 997 | + datetimeChars = tp_inst._defaults.timeFormat.toString() |
| 998 | + .replace(/[hms]/g, '') |
| 999 | + .replace(/TT/g, ampm ? 'APM' : '') |
| 1000 | + .replace(/Tt/g, ampm ? 'AaPpMm' : '') |
| 1001 | + .replace(/tT/g, ampm ? 'AaPpMm' : '') |
| 1002 | + .replace(/T/g, ampm ? 'AP' : '') |
| 1003 | + .replace(/tt/g, ampm ? 'apm' : '') |
| 1004 | + .replace(/t/g, ampm ? 'ap' : '') + |
| 1005 | + " " + |
| 1006 | + tp_inst._defaults.separator + |
| 1007 | + tp_inst._defaults.timeSuffix + |
| 1008 | + (tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') + |
| 1009 | + (tp_inst._defaults.amNames.join('')) + |
| 1010 | + (tp_inst._defaults.pmNames.join('')) + |
| 1011 | + dateChars, |
| 1012 | + chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode); |
| 1013 | + return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1); |
| 1014 | + } |
| 1015 | + } |
| 1016 | + |
| 1017 | + return $.datepicker._base_doKeyPress(event); |
| 1018 | +}; |
| 1019 | + |
| 1020 | +//####################################################################################### |
| 1021 | +// Override key up event to sync manual input changes. |
| 1022 | +//####################################################################################### |
| 1023 | +$.datepicker._base_doKeyUp = $.datepicker._doKeyUp; |
| 1024 | +$.datepicker._doKeyUp = function (event) { |
| 1025 | + var inst = $.datepicker._getInst(event.target), |
| 1026 | + tp_inst = $.datepicker._get(inst, 'timepicker'); |
| 1027 | + |
| 1028 | + if (tp_inst) { |
| 1029 | + if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) { |
| 1030 | + try { |
| 1031 | + $.datepicker._updateDatepicker(inst); |
| 1032 | + } |
| 1033 | + catch (err) { |
| 1034 | + $.datepicker.log(err); |
| 1035 | + } |
| 1036 | + } |
| 1037 | + } |
| 1038 | + |
| 1039 | + return $.datepicker._base_doKeyUp(event); |
| 1040 | +}; |
| 1041 | + |
| 1042 | +//####################################################################################### |
| 1043 | +// override "Today" button to also grab the time. |
| 1044 | +//####################################################################################### |
| 1045 | +$.datepicker._base_gotoToday = $.datepicker._gotoToday; |
| 1046 | +$.datepicker._gotoToday = function(id) { |
| 1047 | + var inst = this._getInst($(id)[0]), |
| 1048 | + $dp = inst.dpDiv; |
| 1049 | + this._base_gotoToday(id); |
| 1050 | + var now = new Date(); |
| 1051 | + var tp_inst = this._get(inst, 'timepicker'); |
| 1052 | + if (tp_inst._defaults.showTimezone && tp_inst.timezone_select) { |
| 1053 | + var tzoffset = now.getTimezoneOffset(); // If +0100, returns -60 |
| 1054 | + var tzsign = tzoffset > 0 ? '-' : '+'; |
| 1055 | + tzoffset = Math.abs(tzoffset); |
| 1056 | + var tzmin = tzoffset % 60 |
| 1057 | + tzoffset = tzsign + ('0' + (tzoffset - tzmin) / 60).slice(-2) + ('0' + tzmin).slice(-2); |
| 1058 | + if (tp_inst._defaults.timezoneIso8609) |
| 1059 | + tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3); |
| 1060 | + tp_inst.timezone_select.val(tzoffset); |
| 1061 | + } |
| 1062 | + this._setTime(inst, now); |
| 1063 | + $( '.ui-datepicker-today', $dp).click(); |
| 1064 | +}; |
| 1065 | + |
| 1066 | +//####################################################################################### |
| 1067 | +// Disable & enable the Time in the datetimepicker |
| 1068 | +//####################################################################################### |
| 1069 | +$.datepicker._disableTimepickerDatepicker = function(target, date, withDate) { |
| 1070 | + var inst = this._getInst(target), |
| 1071 | + tp_inst = this._get(inst, 'timepicker'); |
| 1072 | + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] |
| 1073 | + if (tp_inst) { |
| 1074 | + tp_inst._defaults.showTimepicker = false; |
| 1075 | + tp_inst._updateDateTime(inst); |
| 1076 | + } |
| 1077 | +}; |
| 1078 | + |
| 1079 | +$.datepicker._enableTimepickerDatepicker = function(target, date, withDate) { |
| 1080 | + var inst = this._getInst(target), |
| 1081 | + tp_inst = this._get(inst, 'timepicker'); |
| 1082 | + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] |
| 1083 | + if (tp_inst) { |
| 1084 | + tp_inst._defaults.showTimepicker = true; |
| 1085 | + tp_inst._addTimePicker(inst); // Could be disabled on page load |
| 1086 | + tp_inst._updateDateTime(inst); |
| 1087 | + } |
| 1088 | +}; |
| 1089 | + |
| 1090 | +//####################################################################################### |
| 1091 | +// Create our own set time function |
| 1092 | +//####################################################################################### |
| 1093 | +$.datepicker._setTime = function(inst, date) { |
| 1094 | + var tp_inst = this._get(inst, 'timepicker'); |
| 1095 | + if (tp_inst) { |
| 1096 | + var defaults = tp_inst._defaults, |
| 1097 | + // calling _setTime with no date sets time to defaults |
| 1098 | + hour = date ? date.getHours() : defaults.hour, |
| 1099 | + minute = date ? date.getMinutes() : defaults.minute, |
| 1100 | + second = date ? date.getSeconds() : defaults.second, |
| 1101 | + millisec = date ? date.getMilliseconds() : defaults.millisec; |
| 1102 | + |
| 1103 | + //check if within min/max times.. |
| 1104 | + if ((hour < defaults.hourMin || hour > defaults.hourMax) || (minute < defaults.minuteMin || minute > defaults.minuteMax) || (second < defaults.secondMin || second > defaults.secondMax) || (millisec < defaults.millisecMin || millisec > defaults.millisecMax)) { |
| 1105 | + hour = defaults.hourMin; |
| 1106 | + minute = defaults.minuteMin; |
| 1107 | + second = defaults.secondMin; |
| 1108 | + millisec = defaults.millisecMin; |
| 1109 | + } |
| 1110 | + |
| 1111 | + tp_inst.hour = hour; |
| 1112 | + tp_inst.minute = minute; |
| 1113 | + tp_inst.second = second; |
| 1114 | + tp_inst.millisec = millisec; |
| 1115 | + |
| 1116 | + if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour); |
| 1117 | + if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute); |
| 1118 | + if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second); |
| 1119 | + if (tp_inst.millisec_slider) tp_inst.millisec_slider.slider('value', millisec); |
| 1120 | + |
| 1121 | + tp_inst._onTimeChange(); |
| 1122 | + tp_inst._updateDateTime(inst); |
| 1123 | + } |
| 1124 | +}; |
| 1125 | + |
| 1126 | +//####################################################################################### |
| 1127 | +// Create new public method to set only time, callable as $().datepicker('setTime', date) |
| 1128 | +//####################################################################################### |
| 1129 | +$.datepicker._setTimeDatepicker = function(target, date, withDate) { |
| 1130 | + var inst = this._getInst(target), |
| 1131 | + tp_inst = this._get(inst, 'timepicker'); |
| 1132 | + |
| 1133 | + if (tp_inst) { |
| 1134 | + this._setDateFromField(inst); |
| 1135 | + var tp_date; |
| 1136 | + if (date) { |
| 1137 | + if (typeof date == "string") { |
| 1138 | + tp_inst._parseTime(date, withDate); |
| 1139 | + tp_date = new Date(); |
| 1140 | + tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); |
| 1141 | + } |
| 1142 | + else tp_date = new Date(date.getTime()); |
| 1143 | + if (tp_date.toString() == 'Invalid Date') tp_date = undefined; |
| 1144 | + this._setTime(inst, tp_date); |
| 1145 | + } |
| 1146 | + } |
| 1147 | + |
| 1148 | +}; |
| 1149 | + |
| 1150 | +//####################################################################################### |
| 1151 | +// override setDate() to allow setting time too within Date object |
| 1152 | +//####################################################################################### |
| 1153 | +$.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; |
| 1154 | +$.datepicker._setDateDatepicker = function(target, date) { |
| 1155 | + var inst = this._getInst(target), |
| 1156 | + tp_date = (date instanceof Date) ? new Date(date.getTime()) : date; |
| 1157 | + |
| 1158 | + this._updateDatepicker(inst); |
| 1159 | + this._base_setDateDatepicker.apply(this, arguments); |
| 1160 | + this._setTimeDatepicker(target, tp_date, true); |
| 1161 | +}; |
| 1162 | + |
| 1163 | +//####################################################################################### |
| 1164 | +// override getDate() to allow getting time too within Date object |
| 1165 | +//####################################################################################### |
| 1166 | +$.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker; |
| 1167 | +$.datepicker._getDateDatepicker = function(target, noDefault) { |
| 1168 | + var inst = this._getInst(target), |
| 1169 | + tp_inst = this._get(inst, 'timepicker'); |
| 1170 | + |
| 1171 | + if (tp_inst) { |
| 1172 | + this._setDateFromField(inst, noDefault); |
| 1173 | + var date = this._getDate(inst); |
| 1174 | + if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); |
| 1175 | + return date; |
| 1176 | + } |
| 1177 | + return this._base_getDateDatepicker(target, noDefault); |
| 1178 | +}; |
| 1179 | + |
| 1180 | +//####################################################################################### |
| 1181 | +// override parseDate() because UI 1.8.14 throws an error about "Extra characters" |
| 1182 | +// An option in datapicker to ignore extra format characters would be nicer. |
| 1183 | +//####################################################################################### |
| 1184 | +$.datepicker._base_parseDate = $.datepicker.parseDate; |
| 1185 | +$.datepicker.parseDate = function(format, value, settings) { |
| 1186 | + var date; |
| 1187 | + try { |
| 1188 | + date = this._base_parseDate(format, value, settings); |
| 1189 | + } catch (err) { |
| 1190 | + // Hack! The error message ends with a colon, a space, and |
| 1191 | + // the "extra" characters. We rely on that instead of |
| 1192 | + // attempting to perfectly reproduce the parsing algorithm. |
| 1193 | + date = this._base_parseDate(format, value.substring(0,value.length-(err.length-err.indexOf(':')-2)), settings); |
| 1194 | + } |
| 1195 | + return date; |
| 1196 | +}; |
| 1197 | + |
| 1198 | +//####################################################################################### |
| 1199 | +// override formatDate to set date with time to the input |
| 1200 | +//####################################################################################### |
| 1201 | +$.datepicker._base_formatDate=$.datepicker._formatDate; |
| 1202 | +$.datepicker._formatDate = function(inst, day, month, year){ |
| 1203 | + var tp_inst = this._get(inst, 'timepicker'); |
| 1204 | + if(tp_inst) |
| 1205 | + { |
| 1206 | + if(day) |
| 1207 | + var b = this._base_formatDate(inst, day, month, year); |
| 1208 | + tp_inst._updateDateTime(); |
| 1209 | + return tp_inst.$input.val(); |
| 1210 | + } |
| 1211 | + return this._base_formatDate(inst); |
| 1212 | +} |
| 1213 | + |
| 1214 | +//####################################################################################### |
| 1215 | +// override options setter to add time to maxDate(Time) and minDate(Time). MaxDate |
| 1216 | +//####################################################################################### |
| 1217 | +$.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker; |
| 1218 | +$.datepicker._optionDatepicker = function(target, name, value) { |
| 1219 | + var inst = this._getInst(target), |
| 1220 | + tp_inst = this._get(inst, 'timepicker'); |
| 1221 | + if (tp_inst) { |
| 1222 | + var min,max,onselect; |
| 1223 | + if (typeof name == 'string') { // if min/max was set with the string |
| 1224 | + if (name==='minDate' || name==='minDateTime' ) |
| 1225 | + min = value; |
| 1226 | + else if (name==='maxDate' || name==='maxDateTime') |
| 1227 | + max = value; |
| 1228 | + else if (name==='onSelect') |
| 1229 | + onselect=value; |
| 1230 | + } else if (typeof name == 'object') { //if min/max was set with the JSON |
| 1231 | + if(name.minDate) |
| 1232 | + min = name.minDate; |
| 1233 | + else if (name.minDateTime) |
| 1234 | + min = name.minDateTime; |
| 1235 | + else if (name.maxDate) |
| 1236 | + max = name.maxDate; |
| 1237 | + else if (name.maxDateTime) |
| 1238 | + max = name.maxDateTime; |
| 1239 | + } |
| 1240 | + if(min){ //if min was set |
| 1241 | + if(min==0) |
| 1242 | + min=new Date(); |
| 1243 | + else |
| 1244 | + min= new Date(min); |
| 1245 | + |
| 1246 | + tp_inst._defaults.minDate = min; |
| 1247 | + tp_inst._defaults.minDateTime = min; |
| 1248 | + } else if (max){ //if max was set |
| 1249 | + if(max==0) |
| 1250 | + max=new Date(); |
| 1251 | + else |
| 1252 | + max= new Date(max); |
| 1253 | + tp_inst._defaults.maxDate = max; |
| 1254 | + tp_inst._defaults.maxDateTime = max; |
| 1255 | + } |
| 1256 | + else if (onselect) |
| 1257 | + tp_inst._defaults.onSelect=onselect; |
| 1258 | + } |
| 1259 | + this._base_optionDatepicker(target, name, value); |
| 1260 | +}; |
| 1261 | + |
| 1262 | +//####################################################################################### |
| 1263 | +// jQuery extend now ignores nulls! |
| 1264 | +//####################################################################################### |
| 1265 | +function extendRemove(target, props) { |
| 1266 | + $.extend(target, props); |
| 1267 | + for (var name in props) |
| 1268 | + if (props[name] === null || props[name] === undefined) |
| 1269 | + target[name] = props[name]; |
| 1270 | + return target; |
| 1271 | +} |
| 1272 | + |
| 1273 | +$.timepicker = new Timepicker(); // singleton instance |
| 1274 | +$.timepicker.version = "0.9.7"; |
| 1275 | + |
| 1276 | +})(jQuery); |
| 1277 | + |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/resources/jquery.ui.timepicker.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 1278 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/Contest.sql |
— | — | @@ -0,0 +1,96 @@ |
| 2 | +-- MySQL version of the database schema for the Contest extension. |
| 3 | +-- Licence: GNU GPL v3+ |
| 4 | +-- Author: Jeroen De Dauw < jeroendedauw@gmail.com > |
| 5 | + |
| 6 | +-- Contests |
| 7 | +CREATE TABLE IF NOT EXISTS /*_*/contests ( |
| 8 | + contest_id SMALLINT unsigned NOT NULL auto_increment PRIMARY KEY, |
| 9 | + contest_name VARCHAR(255) NOT NULL, -- String indentifier for the contest |
| 10 | + contest_status TINYINT unsigned NOT NULL default '0', -- Status of the contest. One of 0 (DRAFT), 1 (ACTIVE), 2 (FINISHED) |
| 11 | + contest_end varbinary(14) NOT NULL default '', -- End time of the contest (MW timestamp) |
| 12 | + |
| 13 | + contest_rules_page VARCHAR(255) NOT NULL, -- Name of the page with rules |
| 14 | + contest_opportunities VARCHAR(255) NOT NULL, -- Name of the page with opportunities |
| 15 | + contest_intro VARCHAR(255) NOT NULL, -- Name of the page with the intro text |
| 16 | + contest_help VARCHAR(255) NOT NULL, -- Name of the page with contest help |
| 17 | + contest_signup_email VARCHAR(255) NOT NULL, -- Name of the page with the signup email text |
| 18 | + contest_reminder_email VARCHAR(255) NOT NULL, -- Name of the page with the reminder email text |
| 19 | + |
| 20 | + contest_submission_count SMALLINT unsigned NOT NULL -- Number of submissions made to the contest |
| 21 | +) /*$wgDBTableOptions*/; |
| 22 | +CREATE UNIQUE INDEX /*i*/contests_name ON /*_*/contests (contest_name); |
| 23 | +CREATE INDEX /*i*/contests_status_end ON /*_*/contests (contest_status, contest_end); |
| 24 | + |
| 25 | +-- Contestants |
| 26 | +CREATE TABLE IF NOT EXISTS /*_*/contest_contestants ( |
| 27 | + contestant_id INT unsigned NOT NULL auto_increment PRIMARY KEY, -- Contestant id (unique id per user per contest) |
| 28 | + contestant_contest_id SMALLINT unsigned NOT NULL, -- Foreign key on contests.contest_id |
| 29 | + contestant_user_id INT(10) unsigned NOT NULL, -- Foreign key on user.user_id |
| 30 | + contestant_challenge_id INT unsigned NOT NULL, -- Foreign key on contest_challenges.challenge_id |
| 31 | + |
| 32 | + -- These fields will be copied from the user table on contest lock |
| 33 | + contestant_full_name VARCHAR(255) NOT NULL, -- Full name of the contestant |
| 34 | + contestant_user_name VARCHAR(255) NOT NULL, -- User name of the contestant |
| 35 | + contestant_email TINYBLOB NOT NULL, -- Email of the contestant |
| 36 | + |
| 37 | + -- Extra contestant info |
| 38 | + contestant_country VARCHAR(255) NOT NULL, -- Country code of the contestant |
| 39 | + contestant_volunteer TINYINT unsigned NOT NULL, -- If the user is interested in volunteer opportunities |
| 40 | + contestant_wmf TINYINT unsigned NOT NULL, -- If the user is interested in a WMF job |
| 41 | + contestant_cv TINYBLOB NOT NULL, -- URL to the users CV |
| 42 | + |
| 43 | + contestant_submission TINYBLOB NOT NULL, -- URL to the users submission |
| 44 | + |
| 45 | + contestant_rating TINYINT unsigned NOT NULL, -- The average rating of the contestant |
| 46 | + contestant_rating_count SMALLINT unsigned NOT NULL, -- The number of ratings |
| 47 | + contestant_comments SMALLINT unsigned NOT NULL -- The number of comments |
| 48 | +) /*$wgDBTableOptions*/; |
| 49 | +-- TODO: probably need to split indexes, see queries in ContestantPager |
| 50 | +CREATE INDEX /*i*/contest_contestants_contest_id ON /*_*/contest_contestants (contestant_contest_id, contestant_id); |
| 51 | +CREATE INDEX /*i*/contest_contestants_challenge_id ON /*_*/contest_contestants (contestant_challenge_id, contestant_id); |
| 52 | +CREATE INDEX /*i*/contest_contestants_contest_challenge ON /*_*/contest_contestants (contestant_contest_id, contestant_challenge_id); |
| 53 | +CREATE INDEX /*i*/contest_contestants_contest_volunteer ON /*_*/contest_contestants (contestant_contest_id, contestant_volunteer); |
| 54 | +CREATE INDEX /*i*/contest_contestants_challenge_volunteer ON /*_*/contest_contestants (contestant_challenge_id, contestant_volunteer); |
| 55 | +CREATE INDEX /*i*/contest_contestants_contest_wmf ON /*_*/contest_contestants (contestant_contest_id, contestant_wmf); |
| 56 | +CREATE INDEX /*i*/contest_contestants_challenge_wmf ON /*_*/contest_contestants (contestant_challenge_id, contestant_wmf); |
| 57 | +CREATE INDEX /*i*/contest_contestants_contest_comments ON /*_*/contest_contestants (contestant_contest_id, contestant_comments); |
| 58 | +CREATE INDEX /*i*/contest_contestants_challenge_comments ON /*_*/contest_contestants (contestant_challenge_id, contestant_comments); |
| 59 | +CREATE INDEX /*i*/contest_contestants_contest_rating ON /*_*/contest_contestants (contestant_contest_id, contestant_rating); |
| 60 | +CREATE INDEX /*i*/contest_contestants_challenge_rating ON /*_*/contest_contestants (contestant_challenge_id, contestant_rating); |
| 61 | +CREATE INDEX /*i*/contest_contestants_contest_rating_count ON /*_*/contest_contestants (contestant_contest_id, contestant_rating_count); |
| 62 | +CREATE INDEX /*i*/contest_contestants_challenge_rating_count ON /*_*/contest_contestants (contestant_challenge_id, contestant_rating_count); |
| 63 | +CREATE UNIQUE INDEX /*i*/contest_contestants_id_user ON /*_*/contest_contestants (contestant_contest_id, contestant_user_id); |
| 64 | + |
| 65 | +-- Challenges |
| 66 | +CREATE TABLE IF NOT EXISTS /*_*/contest_challenges ( |
| 67 | + challenge_id INT unsigned NOT NULL auto_increment PRIMARY KEY, -- Challenge id |
| 68 | + challenge_contest_id INT unsigned NOT NULL, -- Foreign key on contests.contest_id |
| 69 | + |
| 70 | + challenge_text TEXT NOT NULL, -- Full challenge description |
| 71 | + challenge_title VARCHAR(255) NOT NULL, -- Title of the challenge |
| 72 | + challenge_oneline TEXT NOT NULL -- One line description of the challenge |
| 73 | +) /*$wgDBTableOptions*/; |
| 74 | +CREATE INDEX /*i*/contest_challenges_contest_id ON /*_*/contest_challenges (challenge_contest_id); |
| 75 | +CREATE UNIQUE INDEX /*i*/contest_challenges_title ON /*_*/contest_challenges (challenge_title); |
| 76 | + |
| 77 | +-- Judge votes |
| 78 | +CREATE TABLE IF NOT EXISTS /*_*/contest_votes ( |
| 79 | + vote_id INT unsigned NOT NULL auto_increment PRIMARY KEY, |
| 80 | + vote_contestant_id INT unsigned NOT NULL, -- Foreign key on contest_contestants.contestant_id |
| 81 | + vote_user_id INT(10) unsigned NOT NULL, -- Judge user id |
| 82 | + |
| 83 | + vote_value SMALLINT NOT NULL -- The value of the vote |
| 84 | +) /*$wgDBTableOptions*/; |
| 85 | +CREATE UNIQUE INDEX /*i*/contest_votes_contestant_user ON /*_*/contest_votes (vote_contestant_id, vote_user_id); |
| 86 | +CREATE INDEX /*i*/contest_votes_user ON /*_*/contest_votes (vote_user_id); |
| 87 | + |
| 88 | +-- Judge comments |
| 89 | +CREATE TABLE IF NOT EXISTS /*_*/contest_comments ( |
| 90 | + comment_id INT unsigned NOT NULL auto_increment PRIMARY KEY, |
| 91 | + comment_contestant_id INT unsigned NOT NULL, -- Foreign key on contest_contestants.contestant_id |
| 92 | + comment_user_id INT(10) unsigned NOT NULL, -- Judge user id |
| 93 | + |
| 94 | + comment_text TEXT NOT NULL, -- The comment text |
| 95 | + comment_time varbinary(14) NOT NULL default '' -- The time at which the comment was made |
| 96 | +) /*$wgDBTableOptions*/; |
| 97 | +CREATE INDEX /*i*/contest_comments_id_time ON /*_*/contest_comments (comment_contestant_id, comment_time); |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/Contest.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 98 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/test/ContestValidationTests.php |
— | — | @@ -0,0 +1,57 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Contest form field validation tests cases. |
| 6 | + * |
| 7 | + * @ingroup Contest |
| 8 | + * @since 0.1 |
| 9 | + * |
| 10 | + * @licence GNU GPL v3 |
| 11 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 12 | + */ |
| 13 | +class ContestValidationTests extends MediaWikiTestCase { |
| 14 | + |
| 15 | + /** |
| 16 | + * Tests @see SpecialMyContests::::validateSubmissionField |
| 17 | + */ |
| 18 | + public function testURLValidation() { |
| 19 | + $tests = array( |
| 20 | + 'https://github.com/JeroenDeDauw/smwcon/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53b' => true, |
| 21 | + 'https://github.com/Jeroen-De-Dauw42/smwcon_-42/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53b' => true, |
| 22 | +// 'https://github.com/JeroenDeDauw$/smwcon/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53b' => false, |
| 23 | +// 'https://github.com/JeroenDeDauw/smwcon/tree3/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53b' => false, |
| 24 | +// 'https://github.com/JeroenDeDauw/smwcon/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53' => false, |
| 25 | +// 'https://github.com/JeroenDeDauw/smwcon/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53ba' => false, |
| 26 | +// 'https://github.com/JeroenDeDauw/smwc*/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53b' => false, |
| 27 | + 'in ur unit test, being quite silly' => false, |
| 28 | + ' https://github.com/JeroenDeDauw/smwcon/tree/f9b26ec4ba1101b1f5d4ef76b7ae6ad3dabfb53b ' => true, |
| 29 | + '' => true, |
| 30 | + ' ' => true, |
| 31 | + 'https://github.com/JeroenDeDauw/smwcon' => true, |
| 32 | + 'https://github.com/JeroenDeDauw' => true, |
| 33 | + 'https://gitorious.org/statusnet' => true, |
| 34 | + 'https://gitorious.org/statusnet/mainline/merge_requests/2224' => true, |
| 35 | + ); |
| 36 | + |
| 37 | + foreach ( $tests as $test => $isValdid ) { |
| 38 | + if ( $isValdid ) { |
| 39 | + $this->assertEquals( true, SpecialMyContests::validateSubmissionField( $test ) ); |
| 40 | + } |
| 41 | + else { |
| 42 | + $this->assertFalse( SpecialMyContests::validateSubmissionField( $test ) === true ); |
| 43 | + } |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * Tests @see ContestDBObject::select and @see ContestDBObject::count |
| 49 | + */ |
| 50 | + public function testObjectSelectCount() { |
| 51 | + $classes = array( 'Contest', 'ContestChallenge' ); |
| 52 | + |
| 53 | + foreach ( $classes as $class ) { |
| 54 | + $this->assertEquals( count( $class::s()->select() ), $class::s()->count() ); |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/test/ContestValidationTests.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 59 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/Contest.i18n.php |
— | — | @@ -0,0 +1,1059 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Internationalization file for the Contest extension. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file Contest.i18n.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3+ |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | + |
| 16 | +$messages = array(); |
| 17 | + |
| 18 | +/** English |
| 19 | + * @author Jeroen De Dauw |
| 20 | + */ |
| 21 | +$messages['en'] = array( |
| 22 | + 'contest-desc' => 'Allows users to participate in contest challenges. Judges can discuss and vote on submissions', |
| 23 | + |
| 24 | + // Misc |
| 25 | + 'contest-toplink' => 'My contests', |
| 26 | + |
| 27 | + // Rights |
| 28 | + 'right-contestadmin' => 'Manage contests', |
| 29 | + 'right-contestparticipant' => 'Participate in contests', |
| 30 | + 'right-contestjudge' => 'Judge contest submissions', |
| 31 | + |
| 32 | + // Groups |
| 33 | + 'group-contestadmin' => 'Contest admins', |
| 34 | + 'group-contestadmin-member' => '{{GENDER:$1|contest admin}}', |
| 35 | + 'grouppage-contestadmin' => 'Project:Contest_admins', |
| 36 | + |
| 37 | + 'group-contestparticipant' => 'Contest participants', |
| 38 | + 'group-contestparticipant-member' => '{{GENDER:$1|contest participant}}', |
| 39 | + 'grouppage-contestparticipant' => 'Project:Contest_participants', |
| 40 | + |
| 41 | + 'group-contestjudge' => 'Contest judges', |
| 42 | + 'group-contestjudge-member' => '{{GENDER:$1|contest judge}}', |
| 43 | + 'grouppage-contestjudge' => 'Project:Contest_judges', |
| 44 | + |
| 45 | + // Preferences |
| 46 | + 'prefs-contest' => 'Contests', |
| 47 | + 'contest-prefs-showtoplink' => 'Show a link to [[Special:MyContests|My Contests]] in the user menu.', |
| 48 | + |
| 49 | + // Contest statuses |
| 50 | + 'contest-status-draft' => 'Draft (disabled)', |
| 51 | + 'contest-status-active' => 'Active (enabled)', |
| 52 | + 'contest-status-expired' => 'Expired (enabled, past end date)', |
| 53 | + 'contest-status-finished' => 'Finished (disabled)', |
| 54 | + |
| 55 | + // Special page names |
| 56 | + 'special-contest' => 'View a contest', |
| 57 | + 'special-contests' => 'Manage contests', |
| 58 | + 'special-contestsignup' => 'Sign up for a contest', |
| 59 | + 'special-contestwelcome' => 'View a contest', |
| 60 | + 'special-editcontest' => 'Edit a contest', |
| 61 | + 'special-mycontests' => 'My contests', |
| 62 | + 'specialpages-group-contest' => 'Contests', |
| 63 | + |
| 64 | + // Navigation links |
| 65 | + 'contest-nav-contests' => 'Contests list', |
| 66 | + 'contest-nav-editcontest' => 'Edit contest', |
| 67 | + 'contest-nav-contest' => 'Summary and participants', |
| 68 | + 'contest-nav-contestwelcome' => 'Landing page', |
| 69 | + 'contest-nav-contestsignup' => 'Signup page', |
| 70 | + |
| 71 | + // Special:Contests |
| 72 | + 'contest-special-addnew' => 'Add a new contest', |
| 73 | + 'contest-special-namedoc' => 'The name of the contest is the identifier used in URLs. ie "name" in Special:Contest/name', |
| 74 | + 'contest-special-newname' => 'Contest name', |
| 75 | + 'contest-special-add' => 'Add contest', |
| 76 | + 'contest-special-existing' => 'Existing contests', |
| 77 | + |
| 78 | + 'contest-special-name' => 'Name', |
| 79 | + 'contest-special-status' => 'Status', |
| 80 | + 'contest-special-submissioncount' => 'Submission count', |
| 81 | + 'contest-special-edit' => 'Edit', |
| 82 | + 'contest-special-delete' => 'Delete', |
| 83 | + |
| 84 | + 'contest-special-confirm-delete' => 'Are you sure you want to delete this contest?', |
| 85 | + 'contest-special-delete-failed' => 'Failed to delete the contest.', |
| 86 | + |
| 87 | + // Special:EditContest |
| 88 | + 'editcontest-text' => 'You are editing a contest.', |
| 89 | + 'editcontest-legend' => 'Contest', |
| 90 | + 'contest-edit-name' => 'Contest name', |
| 91 | + 'contest-edit-status' => 'Contest status', |
| 92 | + 'contest-edit-intro' => 'Introduction page', |
| 93 | + 'contest-edit-opportunities' => 'Opportunities page', |
| 94 | + 'contest-edit-rulespage' => 'Rules page', |
| 95 | + 'contest-edit-help' => 'Help page', |
| 96 | + 'contest-edit-signup' => 'Signup e-mail page', |
| 97 | + 'contest-edit-reminder' => 'Reminder e-mail page', |
| 98 | + 'contest-edit-end' => 'Contest end', |
| 99 | + 'contest-edit-exists-already' => 'Note: you are editing an already existing contest, not creating a new one.', |
| 100 | + 'contest-edit-submit' => 'Submit', |
| 101 | + |
| 102 | + 'contest-edit-challenges' => 'Contest challenges', |
| 103 | + 'contest-edit-delete' => 'Delete challenge', |
| 104 | + 'contest-edit-add-first' => 'Add a challenge', |
| 105 | + 'contest-edit-add-another' => 'Add another challenge', |
| 106 | + 'contest-edit-confirm-delete' => 'Are you sure you want to delete this challenge?', |
| 107 | + 'contest-edit-challenge-title' => 'Challenge title', |
| 108 | + 'contest-edit-challenge-text' => 'Challenge text', |
| 109 | + 'contest-edit-challenge-oneline' => 'Short description', |
| 110 | + |
| 111 | + // Special:ContestWelcome |
| 112 | + 'contest-welcome-unknown' => 'There is no contest with the provided name.', |
| 113 | + 'contest-welcome-rules' => 'In order to participate, you are required to agree to', // js i18n |
| 114 | + 'contest-welcome-rules-link' => 'the contest rules', // js i18n |
| 115 | + 'contest-welcome-signup' => 'Signup now', |
| 116 | + 'contest-welcome-js-off' => 'The contest user interface uses JavaScript for an improved interface. Your browser either does not support JavaScript or has JavaScript turned off.', |
| 117 | + 'contest-welcome-accept-challenge' => 'Challenge accepted', |
| 118 | + |
| 119 | + 'contest-welcome-select-header' => 'Select your challenge:', |
| 120 | + |
| 121 | + // Special:ContestSignup & Special:ContestSubmission |
| 122 | + 'contest-signup-unknown' => 'There is no contest with the provided name.', |
| 123 | + 'contest-signup-submit' => 'Sign up', |
| 124 | + 'contest-signup-header' => 'Please fill out the form to complete your registration for $1.', |
| 125 | + 'contest-signup-email' => 'Your e-mail address', |
| 126 | + 'contest-signup-realname' => 'Your real name', |
| 127 | + 'contest-signup-volunteer' => 'I am interested in volunteer opportunities', |
| 128 | + 'contest-signup-wmf' => 'I am interested in working for the Wikimedia Foundation', |
| 129 | + 'contest-signup-cv' => 'Link to your CV', |
| 130 | + 'contest-signup-readrules' => 'I confirm that I have read, and agree to, [[$1|the contest rules]]', |
| 131 | + 'contest-signup-challenge' => 'What challenge do you want to take on?', |
| 132 | + 'contest-signup-finished' => 'This contest has ended. Thanks for your participation!', |
| 133 | + 'contest-signup-draft' => 'This contest has not started yet. Please be patient.', |
| 134 | + 'contest-signup-country' => 'Your country', |
| 135 | + |
| 136 | + 'contest-signup-require-rules' => 'You need to agree to the contest rules.', |
| 137 | + 'contest-signup-require-country' => 'You need to provide your country of residence.', |
| 138 | + 'contest-signup-invalid-email' => 'The e-mail address you provided is not valid.', |
| 139 | + 'contest-signup-invalid-name' => 'The name you provided is too short.', |
| 140 | + 'contest-signup-require-challenge' => 'You must select a challenge.', |
| 141 | + 'contest-signup-invalid-cv' => 'You entered an invalid URL.', |
| 142 | + |
| 143 | + // Special:Contest |
| 144 | + 'contest-contest-title' => 'Contest: $1', |
| 145 | + 'contest-contest-no-results' => 'There are no contestants to display.', |
| 146 | + 'contest-contest-name' => 'Name', |
| 147 | + 'contest-contest-status' => 'Status', |
| 148 | + 'contest-contest-submissioncount' => 'Amount of participants', |
| 149 | + 'contest-contest-contestants' => 'Contest contestants', |
| 150 | + 'contest-contest-contestants-text' => 'To judge an individual entry, click on the entry ID in the left column.', |
| 151 | + |
| 152 | + // Contestant pager |
| 153 | + 'contest-contestant-id' => 'ID', |
| 154 | + 'contest-contestant-challenge-name' => 'Challenge name', |
| 155 | + 'contest-contestant-volunteer' => 'Volunteer', |
| 156 | + 'contest-contestant-wmf' => 'WMF', |
| 157 | + 'contest-contestant-no' => 'No', |
| 158 | + 'contest-contestant-yes' => 'Yes', |
| 159 | + 'contest-contestant-commentcount' => 'Comments', |
| 160 | + 'contest-contestant-overallrating' => 'Rating', |
| 161 | + 'contest-contestant-rating' => '$1 ($2 {{PLURAL:$2|vote|votes}})', |
| 162 | + |
| 163 | + // Special:Contestant |
| 164 | + 'contest-contestant-title' => 'Contestant $1 ($2)', |
| 165 | + 'contest-contestant-header-id' => 'Contestant ID', |
| 166 | + 'contest-contestant-header-contest' => 'Contest name', |
| 167 | + 'contest-contestant-header-challenge' => 'Challenge name', |
| 168 | + 'contest-contestant-header-submission' => 'Submission link', |
| 169 | + 'contest-contestant-header-country' => 'Contestant country', |
| 170 | + 'contest-contestant-header-wmf' => 'Interested in WMF job', |
| 171 | + 'contest-contestant-header-volunteer' => 'Interested in volunteer opportunities', |
| 172 | + 'contest-contestant-header-rating' => 'Rating', |
| 173 | + 'contest-contestant-header-comments' => 'Amount of comments', |
| 174 | + 'contest-contestant-submission-url' => 'Submission', |
| 175 | + 'contest-contestant-notsubmitted' => 'Not submitted yet', |
| 176 | + 'contest-contestant-comments' => 'Comments', |
| 177 | + 'contest-contestant-submit' => 'Save changes', |
| 178 | + 'contest-contestant-comment-by' => 'Comment by $1', |
| 179 | + 'contest-contestant-rate' => 'Rate this contestant', |
| 180 | + 'contest-contestant-not-voted' => 'You have not voted on this participant yet.', |
| 181 | + 'contest-contestant-voted' => 'Your current vote is $1.', |
| 182 | + 'contest-contestant-permalink' => 'Permalink', |
| 183 | + |
| 184 | + // Emails |
| 185 | + 'contest-email-signup-title' => 'Thanks for joining the challenge!', |
| 186 | + 'contest-email-reminder-title' => 'Only $1 {{PLURAL:$1|day|days}} until the end of the challenge!', |
| 187 | + |
| 188 | + // Special:MyContests |
| 189 | + 'contest-mycontests-toplink' => 'My contests', |
| 190 | + 'contest-mycontests-no-contests' => 'You are not participating in any contest.', |
| 191 | + 'contest-mycontests-active-header' => 'Running contests', |
| 192 | + 'contest-mycontests-finished-header' => 'Passed contests', |
| 193 | + 'contest-mycontests-active-text' => 'These are the contests you are currently participating in:', |
| 194 | + 'contest-mycontests-finished-text' => 'These are the past contests you have participated in:', |
| 195 | + 'contest-mycontests-header-contest' => 'Contest', |
| 196 | + 'contest-mycontests-header-challenge' => 'Challenge', |
| 197 | + 'contest-mycontests-signup-success' => 'You have successfully signed up for the $1 contest.', |
| 198 | + 'contest-mycontests-addition-success' => 'You have successfully posted your submission! Thanks for participating in this contest.', |
| 199 | + 'contest-mycontests-updated-success' => 'You have successfully modified your submission.', |
| 200 | + 'contest-mycontests-sessionfail' => 'Your submission could not be saved due to loss of session data. Please try again.', |
| 201 | + |
| 202 | + 'contest-submission-submit' => 'Submit', |
| 203 | + 'contest-submission-unknown' => 'There is no contest with the provided name.', |
| 204 | + 'contest-submission-header' => 'Thanks for participating in this contest! Once you have completed the challenge, you can add a link to your submission below.', |
| 205 | + 'contest-submission-finished' => 'This contest has ended. Thanks for your participation!', |
| 206 | + |
| 207 | + 'contest-submission-submission' => 'Link to your submission', |
| 208 | + 'contest-submission-invalid-url' => 'This URL does not match one of the allowed formats.', |
| 209 | + 'contest-submission-new-submission' => 'You still need to enter the URL to your submission. This needs to be done before the deadline.', |
| 210 | + 'contest-submission-current-submission' => 'This is the URL to your submission, which you can modify untill the deadline.', |
| 211 | + |
| 212 | + // TODO: how can this be done properly in JS? |
| 213 | + 'contest-submission-domains' => 'Submissions are restricted to these sites: $1', |
| 214 | +); |
| 215 | + |
| 216 | +/** Message documentation (Message documentation) |
| 217 | + * @author Jeroen De Dauw |
| 218 | + */ |
| 219 | +$messages['qqq'] = array( |
| 220 | + 'contest-special-name' => 'Table column header', |
| 221 | + 'contest-special-status' => 'Table column header', |
| 222 | + 'contest-special-submissioncount' => 'Table column header', |
| 223 | + 'contest-special-edit' => 'Table column header', |
| 224 | + 'contest-special-delete' => 'Table column header', |
| 225 | + 'editcontest-text' => 'Short text displayed at the top of the page notifying the user they are editing a contest', |
| 226 | + 'contest-edit-name' => 'form field label', |
| 227 | + 'contest-edit-status' => 'form field label', |
| 228 | + 'contest-edit-intro' => 'Form field label', |
| 229 | + 'contest-edit-opportunities' => 'Form field label', |
| 230 | + 'contest-edit-rulespage' => 'Form field label', |
| 231 | + 'contest-edit-help' => 'Form field label', |
| 232 | + 'contest-edit-signup' => 'Form field label', |
| 233 | + 'contest-edit-reminder' => 'Form field label', |
| 234 | + 'contest-edit-end' => 'Form field label', |
| 235 | + 'contest-edit-exists-already' => 'Warning message to show when the contest already exists', |
| 236 | + 'contest-edit-submit' => 'Submit button text', |
| 237 | + 'contest-edit-delete' => 'Delete challange button text', |
| 238 | + 'contest-edit-add-first' => 'Add a challenge button text', |
| 239 | + 'contest-edit-add-another' => 'Add another challenge button text', |
| 240 | + 'contest-edit-confirm-delete' => 'Challange deletion confirmation message', |
| 241 | + 'contest-edit-challenge-title' => 'Form field label', |
| 242 | + 'contest-edit-challenge-text' => 'Form field label', |
| 243 | + 'contest-edit-challenge-oneline' => 'Form field label', |
| 244 | + 'contest-contest-title' => 'Page title', |
| 245 | + 'contest-contest-no-results' => 'Message displayed instead of a table when there are no contests', |
| 246 | + 'contest-contest-name' => 'Table row header', |
| 247 | + 'contest-contest-status' => 'Table row header', |
| 248 | + 'contest-contest-submissioncount' => 'Table row header', |
| 249 | + 'contest-contest-contestants' => 'Page section header', |
| 250 | + 'contest-contestant-id' => 'Table column header', |
| 251 | + 'contest-contestant-volunteer' => 'Table column header', |
| 252 | + 'contest-contestant-wmf' => 'Table column header', |
| 253 | + 'contest-contestant-no' => 'Table cell value', |
| 254 | + 'contest-contestant-yes' => 'Table cell value', |
| 255 | + 'contest-contestant-commentcount' => 'Table column header', |
| 256 | + 'contest-contestant-overallrating' => 'Table column header', |
| 257 | + 'contest-contestant-rating' => '$1 is the avarage rating, $2 is the amount of votes', |
| 258 | + 'contest-contestant-title' => 'Page title with contestant id $1 and contest name $2', |
| 259 | + 'contest-contestant-header-id' => 'Table row header', |
| 260 | + 'contest-contestant-header-contest' => 'Table row header', |
| 261 | + 'contest-contestant-header-challenge' => 'Table row header', |
| 262 | + 'contest-contestant-header-submission' => 'Table row header', |
| 263 | + 'contest-contestant-header-country' => 'Table row header', |
| 264 | + 'contest-contestant-header-wmf' => 'Table row header', |
| 265 | + 'contest-contestant-header-volunteer' => 'Table row header', |
| 266 | + 'contest-contestant-header-rating' => 'Table row header', |
| 267 | + 'contest-contestant-header-comments' => 'Table row header', |
| 268 | + 'contest-contestant-submission-url' => 'Text for the link to the submission', |
| 269 | + 'contest-contestant-comments' => 'Page header (h2)', |
| 270 | + 'contest-contestant-submit' => 'Submit button text', |
| 271 | + 'contest-contestant-comment-by' => '$1 the user name, linked to the user page, followed by talk, contrib and block links', |
| 272 | + 'contest-contestant-rate' => 'Page header (h2)', |
| 273 | + 'contest-contestant-voted' => '$1 is an integer', |
| 274 | + 'contest-contestant-permalink' => 'Hover-text for comment permalinks', |
| 275 | + 'contest-email-signup-title' => 'Title for signup e-mails', |
| 276 | + 'contest-email-reminder-title' => 'Title for reminder e-mails', |
| 277 | + |
| 278 | + // Special:MyContests |
| 279 | + 'contest-mycontests-toplink' => 'Text for link in the user menu (i.e. where watchlist and preferences are linked)', |
| 280 | + 'contest-mycontests-no-contests' => 'Message indicating there are no contests for the user, displayed instead of a list.', |
| 281 | + 'contest-mycontests-active-header' => 'Page header (h2)', |
| 282 | + 'contest-mycontests-finished-header' => 'Page header (h2)', |
| 283 | + 'contest-mycontests-active-text' => 'A list of all contests that a user is part of', |
| 284 | + 'contest-mycontests-finished-text' => 'A list of all contests a user has been part of', |
| 285 | + 'contest-mycontests-header-contest' => 'Contest', |
| 286 | + 'contest-mycontests-header-challenge' => 'Header', |
| 287 | + 'contest-mycontests-signup-success' => 'Message shown when a user successfully signs up for a contest. $1 is the name of the contest.', |
| 288 | + 'contest-mycontests-addition-success' => 'Message shown when a user has added a submission', |
| 289 | + 'contest-mycontests-updated-success' => 'Message shown when a user has editied a submission', |
| 290 | + 'contest-mycontests-sessionfail' => 'Session failure', |
| 291 | +); |
| 292 | + |
| 293 | +/** German (Deutsch) |
| 294 | + * @author Kghbln |
| 295 | + */ |
| 296 | +$messages['de'] = array( |
| 297 | + 'contest-desc' => 'Ermöglicht Wettbewerbe sowie die anschließende Ermittlung der Gewinner durch Juroren', |
| 298 | + 'contest-toplink' => 'Meine Wettbewerbe', |
| 299 | + 'right-contestadmin' => 'Wettbewerbe verwalten', |
| 300 | + 'right-contestparticipant' => 'An Wettbewerben teilnehmen', |
| 301 | + 'right-contestjudge' => 'Wettbewerbsbeiträge beurteilen', |
| 302 | + 'group-contestadmin' => 'Wettbewerbsadministratoren', |
| 303 | + 'group-contestadmin-member' => '{{GENDER:$1|Wettbewerbsadministrator|Wettbewerbsadministratorin}}', |
| 304 | + 'grouppage-contestadmin' => 'Project:Wettbewerbsadministratoren', |
| 305 | + 'group-contestparticipant' => 'Wettbewerbsteilnehmer', |
| 306 | + 'group-contestparticipant-member' => '{{GENDER:$1|Wettbewerbsteilnehmer|Wettbewerbsteilnehmerin}}', |
| 307 | + 'grouppage-contestparticipant' => 'Project:Wettbewerbsteilnehmer', |
| 308 | + 'group-contestjudge' => 'Wettbewerbsjuroren', |
| 309 | + 'group-contestjudge-member' => '{{GENDER:$1|Wettbewerbsjuror|Wettbewerbsjurorin}}', |
| 310 | + 'grouppage-contestjudge' => 'Project:Wettbewerbsjuroren', |
| 311 | + 'prefs-contest' => 'Wettbewerbe', |
| 312 | + 'contest-prefs-showtoplink' => 'Einen Link zu [[Special:MyContests|Meine Wettbewerbe]] im Benutzermenü anzeigen', |
| 313 | + 'contest-status-draft' => 'Entwurf (deaktiviert)', |
| 314 | + 'contest-status-active' => 'Aktiv (aktiviert)', |
| 315 | + 'contest-status-expired' => 'Abgelaufen (aktiviert, Enddatum verstrichen)', |
| 316 | + 'contest-status-finished' => 'Beendet (deaktiviert)', |
| 317 | + 'special-contest' => 'Einen Wettbewerb ansehen', |
| 318 | + 'special-contests' => 'Wettbewerbe verwalten', |
| 319 | + 'special-contestsignup' => 'Zu einem Wettbewerb anmelden', |
| 320 | + 'special-contestwelcome' => 'Einen Wettbewerb ansehen', |
| 321 | + 'special-editcontest' => 'Einen Wettbewerb bearbeiten', |
| 322 | + 'special-mycontests' => 'Meine Wettbewerbe', |
| 323 | + 'specialpages-group-contest' => 'Wettbewerbe', |
| 324 | + 'contest-nav-contests' => 'Wettbewerbsliste', |
| 325 | + 'contest-nav-editcontest' => 'Wettbewerb bearbeiten', |
| 326 | + 'contest-nav-contest' => 'Zusammenfassung und Teilnehmer', |
| 327 | + 'contest-nav-contestwelcome' => 'Zielseite', |
| 328 | + 'contest-nav-contestsignup' => 'Registrierungsseite', |
| 329 | + 'contest-special-addnew' => 'Einen neuen Wettbewerb hinzufügen', |
| 330 | + 'contest-special-namedoc' => 'Der Name des Wettbewerbs ist der Bezeichner, der in den URLs verwendet wird, bspw. „Name“ auf Spezial:Wettbewerbe/Name', |
| 331 | + 'contest-special-newname' => 'Wettbewerbsname', |
| 332 | + 'contest-special-add' => 'Wettbewerb hinzufügen', |
| 333 | + 'contest-special-existing' => 'Vorhandene Wettbewerbe', |
| 334 | + 'contest-special-name' => 'Name', |
| 335 | + 'contest-special-status' => 'Status', |
| 336 | + 'contest-special-submissioncount' => 'Wettbewerbsbeiträge', |
| 337 | + 'contest-special-edit' => 'Bearbeiten', |
| 338 | + 'contest-special-delete' => 'Löschen', |
| 339 | + 'contest-special-confirm-delete' => 'Soll dieser Wettbewerb tatsächlich gelöscht werden?', |
| 340 | + 'contest-special-delete-failed' => 'Der Wettbewerb konnte nicht gelöscht werden.', |
| 341 | + 'editcontest-text' => 'Du bearbeitest einen Wettbewerb.', |
| 342 | + 'editcontest-legend' => 'Wettbewerb', |
| 343 | + 'contest-edit-name' => 'Wettbewerbsname', |
| 344 | + 'contest-edit-status' => 'Wettbewerbsstatus', |
| 345 | + 'contest-edit-intro' => 'Seite mit der Einführung', |
| 346 | + 'contest-edit-opportunities' => 'Seite mit den Gewinnmöglichkeiten', |
| 347 | + 'contest-edit-rulespage' => 'Seite mit den Regeln', |
| 348 | + 'contest-edit-help' => 'Hilfeseite', |
| 349 | + 'contest-edit-signup' => 'Seite mit der Anmeldungs-E-Mail', |
| 350 | + 'contest-edit-reminder' => 'Seite mit der Erinnerungs-E-Mail', |
| 351 | + 'contest-edit-end' => 'Wettbewerbsende', |
| 352 | + 'contest-edit-exists-already' => 'Hinweis: Du erstellst gerade keinen neuen, sondern bearbeitest einen bereits vorhandenen Wettbewerb.', |
| 353 | + 'contest-edit-submit' => 'Speichern', |
| 354 | + 'contest-edit-challenges' => 'Wettbewerbsherausforderungen', |
| 355 | + 'contest-edit-delete' => 'Herausforderung löschen', |
| 356 | + 'contest-edit-add-first' => 'Eine Herausforderung hinzufügen', |
| 357 | + 'contest-edit-add-another' => 'Eine weitere Herausforderung hinzufügen', |
| 358 | + 'contest-edit-confirm-delete' => 'Soll diese Herausforderung tatsächlich gelöscht werden?', |
| 359 | + 'contest-edit-challenge-title' => 'Name der Herausforderung', |
| 360 | + 'contest-edit-challenge-text' => 'Text der Herausforderung', |
| 361 | + 'contest-edit-challenge-oneline' => 'Kurzbeschreibung', |
| 362 | + 'contest-welcome-unknown' => 'Es ist zum angegebenen Namen kein Wettbewerb vorhanden.', |
| 363 | + 'contest-welcome-rules' => 'Um teilnehmen zu können, muss Folgendem zugestimmt werden:', |
| 364 | + 'contest-welcome-rules-link' => 'die Wettbewerbsregeln', |
| 365 | + 'contest-welcome-signup' => 'Jetzt registrieren', |
| 366 | + 'contest-welcome-js-off' => 'Die Benutzeroberfläche der Wettbewerbe nutzt JavaScript für eine bessere Darstellung. Dein Browser unterstützt entweder kein JavaScript oder es wurde deaktiviert.', |
| 367 | + 'contest-welcome-accept-challenge' => 'Herausforderung angenommen', |
| 368 | + 'contest-welcome-select-header' => 'Herausforderung auswählen:', |
| 369 | + 'contest-signup-unknown' => 'Es ist zum angegebenen Namen kein Wettbewerb vorhanden.', |
| 370 | + 'contest-signup-submit' => 'Registrieren', |
| 371 | + 'contest-signup-header' => 'Bitte das Formular zur Registrierung für $1 ausfüllen.', |
| 372 | + 'contest-signup-email' => 'Deine E-Mail-Adresse', |
| 373 | + 'contest-signup-realname' => 'Dein bürgerlicher Name', |
| 374 | + 'contest-signup-volunteer' => 'Ich bin daran interessiert, mich als Freiwilliger zu beteiligen', |
| 375 | + 'contest-signup-wmf' => 'Ich bin daran interessiert für die Wikimedia Foundation zu arbeiten', |
| 376 | + 'contest-signup-cv' => 'Link zum Lebenslauf', |
| 377 | + 'contest-signup-readrules' => 'Ich bestätige, dass ich [[$1|die Teilnahmebedingungen]] gelesen habe und ihnen zustimme', |
| 378 | + 'contest-signup-challenge' => 'Welche Herausforderung möchtest du annehmen?', |
| 379 | + 'contest-signup-finished' => 'Dieser Wettbewerb ist beendet. Vielen Dank für die Teilnahme.', |
| 380 | + 'contest-signup-draft' => 'Der Wettbewerb hat noch nicht begonnen. Wir bitten um etwas Geduld.', |
| 381 | + 'contest-signup-country' => 'Dein Land', |
| 382 | + 'contest-signup-require-rules' => 'Du musst den Teilnahmebedingungen zustimmen.', |
| 383 | + 'contest-signup-require-country' => 'Du muss das Land angeben, in dem du wohnst.', |
| 384 | + 'contest-signup-invalid-email' => 'Die angegebene E-Mail-Adresse ist ungültig.', |
| 385 | + 'contest-signup-invalid-name' => 'Der angegebene Name ist zu kurz.', |
| 386 | + 'contest-signup-require-challenge' => 'Du musst eine Herausforderung annehmen.', |
| 387 | + 'contest-signup-invalid-cv' => 'Die angegebene URL ist ungültig.', |
| 388 | + 'contest-contest-title' => 'Wettbewerb: $1', |
| 389 | + 'contest-contest-no-results' => 'Es gibt keine Teilnehmer zum Anzeigen.', |
| 390 | + 'contest-contest-name' => 'Name', |
| 391 | + 'contest-contest-status' => 'Status', |
| 392 | + 'contest-contest-submissioncount' => 'Anzahl der Teilnehmer', |
| 393 | + 'contest-contest-contestants' => 'Wettbewerbsteilnehmer', |
| 394 | + 'contest-contest-contestants-text' => 'Um einen bestimmten Wettbewerbsbeitrag zu beurteilen, bitte auf die entsprechende Beitragskennung in der linken Spalte klicken.', |
| 395 | + 'contest-contestant-id' => 'Kennung', |
| 396 | + 'contest-contestant-challenge-name' => 'Name der Herausforderung', |
| 397 | + 'contest-contestant-volunteer' => 'Freiwilliger', |
| 398 | + 'contest-contestant-wmf' => 'WMF', |
| 399 | + 'contest-contestant-no' => 'Nein', |
| 400 | + 'contest-contestant-yes' => 'Ja', |
| 401 | + 'contest-contestant-commentcount' => 'Kommentare', |
| 402 | + 'contest-contestant-overallrating' => 'Bewertung', |
| 403 | + 'contest-contestant-rating' => '$1 ($2 {{PLURAL:$2|Stimme|Stimmen}})', |
| 404 | + 'contest-contestant-title' => 'Teilnehmer $1 ($2)', |
| 405 | + 'contest-contestant-header-id' => 'Teilnehmerkennung', |
| 406 | + 'contest-contestant-header-contest' => 'Wettbewerbsname', |
| 407 | + 'contest-contestant-header-challenge' => 'Name der Herausforderung', |
| 408 | + 'contest-contestant-header-submission' => 'Link zum Beitrag', |
| 409 | + 'contest-contestant-header-country' => 'Teilnehmerland', |
| 410 | + 'contest-contestant-header-wmf' => 'An einer Anstellung bei der WMF interessiert', |
| 411 | + 'contest-contestant-header-volunteer' => 'An freiwilliger Mitarbeit interessiert', |
| 412 | + 'contest-contestant-header-rating' => 'Bewertung', |
| 413 | + 'contest-contestant-header-comments' => 'Anzahl der Kommentare', |
| 414 | + 'contest-contestant-submission-url' => 'Beitrag', |
| 415 | + 'contest-contestant-notsubmitted' => 'Noch nicht eingereicht', |
| 416 | + 'contest-contestant-comments' => 'Kommentare', |
| 417 | + 'contest-contestant-submit' => 'Änderungen speichern', |
| 418 | + 'contest-contestant-comment-by' => 'Kommentar von $1', |
| 419 | + 'contest-contestant-rate' => 'Diese Teilnehmer bewerten', |
| 420 | + 'contest-contestant-not-voted' => 'Du hast noch nicht bezüglich dieses Teilnehmers abgestimmt.', |
| 421 | + 'contest-contestant-voted' => 'Deine aktuelle Stimmenanzahl beträgt $1.', |
| 422 | + 'contest-contestant-permalink' => 'Permanentlink', |
| 423 | + 'contest-email-signup-title' => 'Vielen Dank für die Teilnahme an dieser Herausforderung.', |
| 424 | + 'contest-email-reminder-title' => 'Nur $1 {{PLURAL:$1|Tag|Tage}} bis zum Ablauf des Herausforderungszeitraums.', |
| 425 | + 'contest-mycontests-toplink' => 'Meine Wettbewerbe', |
| 426 | + 'contest-mycontests-no-contests' => 'Du nimmst an keinem Wettbewerb teil.', |
| 427 | + 'contest-mycontests-active-header' => 'Laufende Wettbewerbe', |
| 428 | + 'contest-mycontests-finished-header' => 'Beendete Wettbewerbe', |
| 429 | + 'contest-mycontests-active-text' => 'Dies sind die Wettbewerbe, an denen du derzeit teilnimmst.', |
| 430 | + 'contest-mycontests-finished-text' => 'Dies sind die beendeten Wettbewerbe an denen du teilgenommen hast.', |
| 431 | + 'contest-mycontests-header-contest' => 'Wettbewerb', |
| 432 | + 'contest-mycontests-header-challenge' => 'Herausforderung', |
| 433 | + 'contest-mycontests-signup-success' => 'Du hast dich erfolgreich für diesen Wettbewerb angemeldet.', |
| 434 | + 'contest-mycontests-addition-success' => 'Du hast deinen Beitrag erfolgreich eingereicht. Vielen Dank für die Teilnehme an diesem Wettbewerb.', |
| 435 | + 'contest-mycontests-updated-success' => 'Du hast deinen Beitrag erfolgreich geändert.', |
| 436 | + 'contest-mycontests-sessionfail' => 'Dein Beitrag konnte aufgrund verlorengegangener Sitzungsdaten nicht gespeichert werden. Bitte versuche es erneut.', |
| 437 | + 'contest-submission-submit' => 'Speichern', |
| 438 | + 'contest-submission-unknown' => 'Es ist zum angegebenen Namen kein Wettbewerb vorhanden.', |
| 439 | + 'contest-submission-header' => 'Vielen Dank für deine Teilnahme an diesem Wettbewerb. Nachdem du deine Herausforderung bewältigt hast, kannst du unten den Link zu deinem Beitrag hinzufügen.', |
| 440 | + 'contest-submission-finished' => 'Dieser Wettbewerb ist beendet. Vielen Dank für die Teilnahme.', |
| 441 | + 'contest-submission-submission' => 'Link zu deinem Beitrag', |
| 442 | + 'contest-submission-invalid-url' => 'Diese URL entspricht nicht einem der zulässigen Formate.', |
| 443 | + 'contest-submission-new-submission' => 'Du musst noch den Link zu deinem Beitrag hinzufügen. Dies muss noch vor Ablauf der Wettbewerbsfrist erfolgen.', |
| 444 | + 'contest-submission-current-submission' => 'Dies ist die URL zu deinem Beitrag. Du kannst ihn noch bis zum Ende der Wettbewerbsfrist ändern.', |
| 445 | + 'contest-submission-domains' => 'Beiträge sind auf diese Websites beschränkt: $1', |
| 446 | +); |
| 447 | + |
| 448 | +/** German (formal address) (Deutsch (Sie-Form)) |
| 449 | + * @author Kghbln |
| 450 | + */ |
| 451 | +$messages['de-formal'] = array( |
| 452 | + 'editcontest-text' => 'Sie bearbeiten einen Wettbewerb.', |
| 453 | + 'contest-edit-exists-already' => 'Hinweis: Sie erstellen gerade keinen neuen, sondern bearbeiten einen bereits vorhandenen Wettbewerb.', |
| 454 | + 'contest-welcome-js-off' => 'Die Benutzeroberfläche der Wettbewerbe nutzt JavaScript für eine bessere Darstellung. Ihr Browser unterstützt entweder kein JavaScript oder es wurde deaktiviert.', |
| 455 | + 'contest-signup-email' => 'Ihre E-Mail-Adresse', |
| 456 | + 'contest-signup-realname' => 'Ihr bürgerlicher Name', |
| 457 | + 'contest-signup-challenge' => 'Welche Herausforderung möchten Sie annehmen?', |
| 458 | + 'contest-signup-country' => 'Ihr Land', |
| 459 | + 'contest-signup-require-rules' => 'Sie müssen den Teilnahmebedingungen zustimmen.', |
| 460 | + 'contest-signup-require-country' => 'Sie müssen das Land angeben, in dem Sie wohnen.', |
| 461 | + 'contest-signup-require-challenge' => 'Sie müssen eine Herausforderung annehmen.', |
| 462 | + 'contest-contestant-not-voted' => 'Sie haben noch nicht bezüglich dieses Teilnehmers abgestimmt.', |
| 463 | + 'contest-contestant-voted' => 'Ihre aktuelle Stimmenanzahl beträgt $1.', |
| 464 | + 'contest-mycontests-no-contests' => 'Sie nehmen an keinem Wettbewerb teil.', |
| 465 | + 'contest-mycontests-active-text' => 'Dies sind die Wettbewerbe, an denen Sie derzeit teilnehmen.', |
| 466 | + 'contest-mycontests-finished-text' => 'Dies sind die beendeten Wettbewerbe an denen Sie teilgenommen haben.', |
| 467 | + 'contest-mycontests-signup-success' => 'Sie haben sich erfolgreich für diesen Wettbewerb angemeldet.', |
| 468 | + 'contest-mycontests-addition-success' => 'Sie haben Ihren Beitrag erfolgreich eingereicht. Vielen Dank für die Teilnehme an diesem Wettbewerb.', |
| 469 | + 'contest-mycontests-updated-success' => 'Sie haben Ihren Beitrag erfolgreich geändert.', |
| 470 | + 'contest-mycontests-sessionfail' => 'Ihr Beitrag konnte aufgrund verlorengegangener Sitzungsdaten nicht gespeichert werden. Bitte versuchen Sie es erneut.', |
| 471 | + 'contest-submission-header' => 'Vielen Dank für Ihre Teilnahme an diesem Wettbewerb. Nachdem Sie Ihre Herausforderung bewältigt haben, können Sie unten den Link zu Ihrem Beitrag hinzufügen.', |
| 472 | + 'contest-submission-submission' => 'Link zu Ihrem Beitrag', |
| 473 | + 'contest-submission-new-submission' => 'Sie müssen noch den Link zu Ihrem Beitrag hinzufügen. Dies muss noch vor Ablauf der Wettbewerbsfrist erfolgen.', |
| 474 | + 'contest-submission-current-submission' => 'Dies ist die URL zu Ihrem Beitrag. Sie können ihn noch bis zum Ende der Wettbewerbsfrist ändern.', |
| 475 | +); |
| 476 | + |
| 477 | +/** French (Français) |
| 478 | + * @author Gomoko |
| 479 | + */ |
| 480 | +$messages['fr'] = array( |
| 481 | + 'contest-desc' => 'Extension de concours qui permet aux utilisateurs de participer à des défis de concours organisés par un administrateur. Via une interface de jugement, les juges peuvent discuter et voter sur les propositions.', |
| 482 | + 'contest-toplink' => 'Mes concours', |
| 483 | + 'right-contestadmin' => 'Gérer les concours', |
| 484 | + 'right-contestparticipant' => 'Participer aux concours', |
| 485 | + 'right-contestjudge' => 'Juger les réponses au concours', |
| 486 | + 'group-contestadmin' => 'Administrateurs du concours', |
| 487 | + 'group-contestadmin-member' => '{{GENDER:$1|administrateur du concours}}', |
| 488 | + 'grouppage-contestadmin' => 'Project:Contest_admins', |
| 489 | + 'group-contestparticipant' => 'Participants au concours', |
| 490 | + 'group-contestparticipant-member' => '{{GENDER:$1|participant au concours}}', |
| 491 | + 'grouppage-contestparticipant' => 'Project:Contest_participants', |
| 492 | + 'group-contestjudge' => 'Juges du concours', |
| 493 | + 'group-contestjudge-member' => '{{GENDER:$1|juge du concours}}', |
| 494 | + 'grouppage-contestjudge' => 'Project:Contest_judges', |
| 495 | + 'prefs-contest' => 'Concours', |
| 496 | + 'contest-prefs-showtoplink' => 'Afficher un lien vers [[Special:MyContests|Mes Concours]] dans le menu principal.', |
| 497 | + 'contest-status-draft' => 'Brouillon (désactivé)', |
| 498 | + 'contest-status-active' => 'Actif (activé)', |
| 499 | + 'contest-status-expired' => 'Expiré (activé, date de fin dépassée)', |
| 500 | + 'contest-status-finished' => 'Terminé (désactivé)', |
| 501 | + 'special-contest' => 'Voir un concours', |
| 502 | + 'special-contests' => 'Gérer les concours', |
| 503 | + 'special-contestsignup' => "S'inscrire à un concours", |
| 504 | + 'special-contestwelcome' => 'Afficher un concours', |
| 505 | + 'special-editcontest' => 'Modifier un concours', |
| 506 | + 'special-mycontests' => 'Mes concours', |
| 507 | + 'specialpages-group-contest' => 'Concours', |
| 508 | + 'contest-nav-contests' => 'Liste des concours', |
| 509 | + 'contest-nav-editcontest' => 'Modifier un concours', |
| 510 | + 'contest-nav-contest' => 'Résumé et participants', |
| 511 | + 'contest-nav-contestwelcome' => 'Page de destination', |
| 512 | + 'contest-nav-contestsignup' => 'Page de connexion', |
| 513 | + 'contest-special-addnew' => 'Ajouter un nouveau concours', |
| 514 | + 'contest-special-namedoc' => 'Le nom du concours est l\'identifiant utilisé dans les URLs, soit "nom" dans Special:Contest/nom', |
| 515 | + 'contest-special-newname' => 'Nom du concours', |
| 516 | + 'contest-special-add' => 'Ajouter un concours', |
| 517 | + 'contest-special-existing' => 'Concours existants', |
| 518 | + 'contest-special-name' => 'Nom', |
| 519 | + 'contest-special-status' => 'Statut', |
| 520 | + 'contest-special-submissioncount' => 'Nombre de réponses', |
| 521 | + 'contest-special-edit' => 'Modifier', |
| 522 | + 'contest-special-delete' => 'Supprimer', |
| 523 | + 'contest-special-confirm-delete' => 'Êtes-vous sûrs de vouloir supprimer ce concours?', |
| 524 | + 'contest-special-delete-failed' => 'Échec à la suppression du concours.', |
| 525 | + 'editcontest-text' => 'Vous êtes en train de modifier un concours.', |
| 526 | + 'editcontest-legend' => 'Concours', |
| 527 | + 'contest-edit-name' => 'Nom du concours', |
| 528 | + 'contest-edit-status' => 'Statut du concours', |
| 529 | + 'contest-edit-intro' => "Page d'introduction", |
| 530 | + 'contest-edit-opportunities' => 'Page des possibilités', |
| 531 | + 'contest-edit-rulespage' => 'Page des règles', |
| 532 | + 'contest-edit-help' => "Page d'aide", |
| 533 | + 'contest-edit-signup' => "Page du courriel d'abonnement", |
| 534 | + 'contest-edit-reminder' => 'Page du courriel de rappel', |
| 535 | + 'contest-edit-end' => 'Fin du concours', |
| 536 | + 'contest-edit-exists-already' => "Note: vous êtes en train de modifier un concours déjà existant, non pas d'en créer un nouveau.", |
| 537 | + 'contest-edit-submit' => 'Soumettre', |
| 538 | + 'contest-edit-challenges' => 'Défis du concours', |
| 539 | + 'contest-edit-delete' => 'Supprimer un défi', |
| 540 | + 'contest-edit-add-first' => 'Ajouter un défi', |
| 541 | + 'contest-edit-add-another' => 'Ajouter un autre défi', |
| 542 | + 'contest-edit-confirm-delete' => 'Êtes-vous sûr de vouloir supprimer ce défi?', |
| 543 | + 'contest-edit-challenge-title' => 'Titre du défi', |
| 544 | + 'contest-edit-challenge-text' => 'Texte du défi', |
| 545 | + 'contest-edit-challenge-oneline' => 'Description courte', |
| 546 | + 'contest-welcome-unknown' => "Il n'y a pas de concours avec le nom indiqué.", |
| 547 | + 'contest-welcome-rules' => 'Afin de participer, vous devez accepter', |
| 548 | + 'contest-welcome-rules-link' => 'les règles du concours', |
| 549 | + 'contest-welcome-signup' => "S'inscrire maintenant", |
| 550 | + 'contest-welcome-js-off' => "L'interface utilisateur du concours utilise JavaScript pour une interface améliorée. Votre navigateur, soit ne supporte pas JavaScript, soit a JavaScript désactivé.", |
| 551 | + 'contest-welcome-accept-challenge' => 'Défi accepté', |
| 552 | + 'contest-welcome-select-header' => 'Choisir votre défi:', |
| 553 | + 'contest-signup-unknown' => "Il n'y a pas de concours avec le nom indiqué.", |
| 554 | + 'contest-signup-submit' => "S'inscrire", |
| 555 | + 'contest-signup-header' => 'Merci de remplir le formulaire pour terminer votre inscription pour $1.', |
| 556 | + 'contest-signup-email' => 'Votre adresse de courriel', |
| 557 | + 'contest-signup-realname' => 'Votre vrai nom', |
| 558 | + 'contest-signup-volunteer' => 'Je suis intéressé par les possibilités de bénévolat', |
| 559 | + 'contest-signup-wmf' => 'Je suis intéresse pour travailler pour la Fondation Wikimedia', |
| 560 | + 'contest-signup-cv' => 'Lien vers votre CV', |
| 561 | + 'contest-signup-readrules' => "Je confirme que j'ai lu et approuvé [[$1|les règles du concours]]", |
| 562 | + 'contest-signup-challenge' => 'Quel défi voulez-vous relever?', |
| 563 | + 'contest-signup-finished' => 'Ce concours est terminé. Merci pour votre participation!', |
| 564 | + 'contest-signup-draft' => "Ce concours n'a pas encore commencé. Merci de patienter.", |
| 565 | + 'contest-signup-country' => 'Votre pays', |
| 566 | + 'contest-signup-require-rules' => 'Vous devez accepter les règles du concours.', |
| 567 | + 'contest-signup-require-country' => 'Vous devez indiquer votre pays de résidence.', |
| 568 | + 'contest-signup-invalid-email' => "L'adresse de courriel que vous avez fournie n'est pas valide.", |
| 569 | + 'contest-signup-invalid-name' => 'Le nom que vous avez indiqué est trop court.', |
| 570 | + 'contest-signup-require-challenge' => 'Vous devez sélectionner un défi.', |
| 571 | + 'contest-signup-invalid-cv' => 'Vous avez entré une mauvaise URL.', |
| 572 | + 'contest-contest-title' => 'Concours: $1', |
| 573 | + 'contest-contest-no-results' => "Il n'y a pas de participants à afficher.", |
| 574 | + 'contest-contest-name' => 'Nom', |
| 575 | + 'contest-contest-status' => 'Statut', |
| 576 | + 'contest-contest-submissioncount' => 'Nombre de participants', |
| 577 | + 'contest-contest-contestants' => 'Participants au concours', |
| 578 | + 'contest-contest-contestants-text' => "Pour juger une proposition individuelle, cliquez sur l'ID de celle-ci dans la colonne de gauche.", |
| 579 | + 'contest-contestant-id' => 'ID', |
| 580 | + 'contest-contestant-challenge-name' => 'Nom du défi', |
| 581 | + 'contest-contestant-volunteer' => 'Bénévole', |
| 582 | + 'contest-contestant-wmf' => 'WMF', |
| 583 | + 'contest-contestant-no' => 'Non', |
| 584 | + 'contest-contestant-yes' => 'Oui', |
| 585 | + 'contest-contestant-commentcount' => 'Commentaires', |
| 586 | + 'contest-contestant-overallrating' => 'Évaluation', |
| 587 | + 'contest-contestant-rating' => '$1 ($2 {{PLURAL:$2|vote|votes}})', |
| 588 | + 'contest-contestant-title' => 'Candidat $1 ($2)', |
| 589 | + 'contest-contestant-header-id' => 'ID du candidat', |
| 590 | + 'contest-contestant-header-contest' => 'Nom du concours', |
| 591 | + 'contest-contestant-header-challenge' => 'Nom du défi', |
| 592 | + 'contest-contestant-header-submission' => 'Lien de réponse', |
| 593 | + 'contest-contestant-header-country' => 'Pays du candidat', |
| 594 | + 'contest-contestant-header-wmf' => 'Intéressé par un travail WMF', |
| 595 | + 'contest-contestant-header-volunteer' => 'Intéressé par les possibilités de bénévolat', |
| 596 | + 'contest-contestant-header-rating' => 'Évaluation', |
| 597 | + 'contest-contestant-header-comments' => 'Nombre de commentaires', |
| 598 | + 'contest-contestant-submission-url' => 'Réponse', |
| 599 | + 'contest-contestant-notsubmitted' => 'Pas encore envoyé', |
| 600 | + 'contest-contestant-comments' => 'Commentaires', |
| 601 | + 'contest-contestant-submit' => 'Enregistrer les modifications', |
| 602 | + 'contest-contestant-comment-by' => 'Commentaire par $1', |
| 603 | + 'contest-contestant-rate' => 'Évaluer ce candidat', |
| 604 | + 'contest-contestant-not-voted' => "Vous n'avez pas encore voté pour ce candidat.", |
| 605 | + 'contest-contestant-voted' => 'Votre vote actuel est $1.', |
| 606 | + 'contest-contestant-permalink' => 'Lien permanent', |
| 607 | + 'contest-email-signup-title' => "Merci d'avoir rejoint le défi!", |
| 608 | + 'contest-email-reminder-title' => 'Plus que $1 {{PLURAL:$1|jour|jours}} avant la fin du défi!', |
| 609 | + 'contest-mycontests-toplink' => 'Mes concours', |
| 610 | + 'contest-mycontests-no-contests' => 'Vous ne participez à aucun concours.', |
| 611 | + 'contest-mycontests-active-header' => 'Concours en cours', |
| 612 | + 'contest-mycontests-finished-header' => 'Concours terminés', |
| 613 | + 'contest-mycontests-active-text' => 'Voici les concours auxquels vous participez actuellement.', |
| 614 | + 'contest-mycontests-finished-text' => 'Voici les concours terminés auxquels vous avez participé.', |
| 615 | + 'contest-mycontests-header-contest' => 'Concours', |
| 616 | + 'contest-mycontests-header-challenge' => 'Défi', |
| 617 | + 'contest-mycontests-signup-success' => 'Vous avez réussi à vous inscrire à ce concours.', |
| 618 | + 'contest-mycontests-addition-success' => 'Vous avez réussi à envoyer votre réponse! Merci de votre participation à ce concours.', |
| 619 | + 'contest-mycontests-updated-success' => 'Vous avez réussi à modifier votre réponse.', |
| 620 | + 'contest-mycontests-sessionfail' => "Votre réponse n'a pas pu être enregistrée à cause d'une perte des données de session. Merci d'essayer de nouveau.", |
| 621 | + 'contest-submission-submit' => 'Soumettre', |
| 622 | + 'contest-submission-unknown' => "Il n'y a pas de concours avec le nom indiqué.", |
| 623 | + 'contest-submission-header' => "Merci d'avoir participé à ce concours! Une fois votre défi terminé, vous pouvez ajouter un lien à votre réponse ci-dessous.", |
| 624 | + 'contest-submission-finished' => 'Ce concours est terminé. Merci pour votre participation!', |
| 625 | + 'contest-submission-submission' => 'Lien vers votre réponse', |
| 626 | + 'contest-submission-invalid-url' => 'Cette URL ne correspond pas à un des formats autorisés.', |
| 627 | + 'contest-submission-new-submission' => "Vous devez encore entrer l'URL de votre réponse. Cela doit être fait avant la date limite.", |
| 628 | + 'contest-submission-current-submission' => "Voici l'URL de votre réponse, que vous pouvez modifier jusqu'à la date limite.", |
| 629 | + 'contest-submission-domains' => 'Les réponses sont limitées à ces sites: $1', |
| 630 | +); |
| 631 | + |
| 632 | +/** Interlingua (Interlingua) |
| 633 | + * @author McDutchie |
| 634 | + */ |
| 635 | +$messages['ia'] = array( |
| 636 | + 'contest-desc' => 'Permitte al usatores de participar in concursos definite per administratores. Via un interfacie de judicamento, le judices pote discuter e votar sur submissiones.', |
| 637 | + 'contest-toplink' => 'Mi concursos', |
| 638 | + 'right-contestadmin' => 'Gerer concursos', |
| 639 | + 'right-contestparticipant' => 'Participar in concursos', |
| 640 | + 'right-contestjudge' => 'Judicar submissiones de concurso', |
| 641 | + 'group-contestadmin' => 'Administratores de concurso', |
| 642 | + 'group-contestadmin-member' => '{{GENDER:$1|administrator|administratrice}} de concurso', |
| 643 | + 'grouppage-contestadmin' => 'Project:Administratores de concurso', |
| 644 | + 'group-contestparticipant' => 'Participantes del concurso', |
| 645 | + 'group-contestparticipant-member' => '{{GENDER:$1|participante}} de concurso', |
| 646 | + 'grouppage-contestparticipant' => 'Project:Participantes de concurso', |
| 647 | + 'group-contestjudge' => 'Judices de concurso', |
| 648 | + 'group-contestjudge-member' => '{{GENDER:$1|judice}} de concurso', |
| 649 | + 'grouppage-contestjudge' => 'Project:Judices de concurso', |
| 650 | + 'prefs-contest' => 'Concursos', |
| 651 | + 'contest-prefs-showtoplink' => 'Monstrar un ligamine a [[Special:MyContests|Mi concursos]] in le menu al initio.', |
| 652 | + 'contest-status-draft' => 'Version provisori (disactivate)', |
| 653 | + 'contest-status-active' => 'Active (activate)', |
| 654 | + 'contest-status-expired' => 'Expirate (activate, data de fin passate)', |
| 655 | + 'contest-status-finished' => 'Finite (disactivate)', |
| 656 | + 'special-contest' => 'Vider un concurso', |
| 657 | + 'special-contests' => 'Gerer concursos', |
| 658 | + 'special-contestsignup' => 'Inscriber se pro un concurso', |
| 659 | + 'special-contestwelcome' => 'Vider un concurso', |
| 660 | + 'special-editcontest' => 'Modificar un concurso', |
| 661 | + 'special-mycontests' => 'Mi concursos', |
| 662 | + 'specialpages-group-contest' => 'Concursos', |
| 663 | + 'contest-nav-contests' => 'Lista de concursos', |
| 664 | + 'contest-nav-editcontest' => 'Modificar concurso', |
| 665 | + 'contest-nav-contest' => 'Summario e participantes', |
| 666 | + 'contest-nav-contestwelcome' => 'Pagina de arrivata', |
| 667 | + 'contest-nav-contestsignup' => 'Pagina de inscription', |
| 668 | + 'contest-special-addnew' => 'Adder un nove concurso', |
| 669 | + 'contest-special-namedoc' => 'Le nomine del concurso es le identificator usate in URLs, i.e. "nomine" in Special:Concurso/nomine', |
| 670 | + 'contest-special-newname' => 'Nomine del concurso', |
| 671 | + 'contest-special-add' => 'Adder concurso', |
| 672 | + 'contest-special-existing' => 'Concursos existente', |
| 673 | + 'contest-special-name' => 'Nomine', |
| 674 | + 'contest-special-status' => 'Stato', |
| 675 | + 'contest-special-submissioncount' => 'Numero de submissiones', |
| 676 | + 'contest-special-edit' => 'Modificar', |
| 677 | + 'contest-special-delete' => 'Deler', |
| 678 | + 'contest-special-confirm-delete' => 'Es tu secur de voler deler iste concurso?', |
| 679 | + 'contest-special-delete-failed' => 'Le deletion del concurso ha fallite.', |
| 680 | + 'editcontest-text' => 'Tu modifica ora un concurso.', |
| 681 | + 'editcontest-legend' => 'Concurso', |
| 682 | + 'contest-edit-name' => 'Nomine del concurso', |
| 683 | + 'contest-edit-status' => 'Stato del concurso', |
| 684 | + 'contest-edit-intro' => 'Pagina de introduction', |
| 685 | + 'contest-edit-opportunities' => 'Pagina de opportunitates', |
| 686 | + 'contest-edit-rulespage' => 'Pagina de regulas', |
| 687 | + 'contest-edit-help' => 'Pagina de adjuta', |
| 688 | + 'contest-edit-signup' => 'Pagina de e-mail de inscription', |
| 689 | + 'contest-edit-reminder' => 'Pagina de e-mail de rememoration', |
| 690 | + 'contest-edit-end' => 'Fin del concurso', |
| 691 | + 'contest-edit-exists-already' => 'Nota: ora tu modifica un concurso jam existente, tu non crea un nove.', |
| 692 | + 'contest-edit-submit' => 'Submitter', |
| 693 | + 'contest-edit-challenges' => 'Defias de concurso', |
| 694 | + 'contest-edit-delete' => 'Deler defia', |
| 695 | + 'contest-edit-add-first' => 'Adder un defia', |
| 696 | + 'contest-edit-add-another' => 'Adder un altere defia', |
| 697 | + 'contest-edit-confirm-delete' => 'Es tu secur de voler deler iste defia?', |
| 698 | + 'contest-edit-challenge-title' => 'Titulo del defia', |
| 699 | + 'contest-edit-challenge-text' => 'Texto del defia', |
| 700 | + 'contest-edit-challenge-oneline' => 'Curte description', |
| 701 | + 'contest-welcome-unknown' => 'Non existe un concurso con le nomine specificate.', |
| 702 | + 'contest-welcome-rules' => 'Pro poter participar, es necessari declarar se de accordo con', |
| 703 | + 'contest-welcome-rules-link' => 'le regulas del concurso', |
| 704 | + 'contest-welcome-signup' => 'Inscriber ora', |
| 705 | + 'contest-welcome-js-off' => 'Le interfacie de usator del concurso usa JavaScript pro un experientia melior. Ma tu navigator non supporta JavaScript o lo ha disactivate.', |
| 706 | + 'contest-welcome-accept-challenge' => 'Defia acceptate', |
| 707 | + 'contest-welcome-select-header' => 'Selige tu defia:', |
| 708 | + 'contest-signup-unknown' => 'Non existe un concurso con le nomine specificate.', |
| 709 | + 'contest-signup-submit' => 'Inscriber', |
| 710 | + 'contest-signup-header' => 'Per favor plena le formulario pro completar le inscription pro $1.', |
| 711 | + 'contest-signup-email' => 'Adresse de e-mail', |
| 712 | + 'contest-signup-realname' => 'Nomine real', |
| 713 | + 'contest-signup-volunteer' => 'Io ha interesse in devenir voluntario', |
| 714 | + 'contest-signup-wmf' => 'Io ha interesse in laborar pro le Fundation Wikimedia', |
| 715 | + 'contest-signup-cv' => 'Ligamine a tu CV', |
| 716 | + 'contest-signup-readrules' => 'Io confirma que io ha legite, e es de accordo con, [[$1|le regulas del concurso]]', |
| 717 | + 'contest-signup-challenge' => 'Qual defia vole tu acceptar?', |
| 718 | + 'contest-signup-finished' => 'Iste concurso ha finite. Gratias pro haber participate!', |
| 719 | + 'contest-signup-draft' => 'Iste concurso non ha ancora comenciate. Per favor sia patiente.', |
| 720 | + 'contest-signup-country' => 'Pais', |
| 721 | + 'contest-signup-require-rules' => 'Es necessari declarar se de accordo con le regulas del concurso.', |
| 722 | + 'contest-signup-require-country' => 'Es necessari fornir le pais ubi tu reside.', |
| 723 | + 'contest-signup-invalid-email' => 'Le adresse de e-mail que tu ha specificate non es valide.', |
| 724 | + 'contest-signup-invalid-name' => 'Le nomine que tu ha specificate es troppo curte.', |
| 725 | + 'contest-signup-require-challenge' => 'Es necessari seliger un defia.', |
| 726 | + 'contest-signup-invalid-cv' => 'Tu ha specificate un URL invalide.', |
| 727 | + 'contest-contest-title' => 'Concurso: $1', |
| 728 | + 'contest-contest-no-results' => 'Il non ha concurrentes a monstrar.', |
| 729 | + 'contest-contest-name' => 'Nomine', |
| 730 | + 'contest-contest-status' => 'Stato', |
| 731 | + 'contest-contest-submissioncount' => 'Numero de participantes', |
| 732 | + 'contest-contest-contestants' => 'Concurrentes del concurso', |
| 733 | + 'contest-contest-contestants-text' => 'Pro judicar un entrata individual, clicca sur le ID del entrata in le columna sinistre.', |
| 734 | + 'contest-contestant-id' => 'ID', |
| 735 | + 'contest-contestant-challenge-name' => 'Nomine del defia', |
| 736 | + 'contest-contestant-volunteer' => 'Voluntario', |
| 737 | + 'contest-contestant-wmf' => 'WMF', |
| 738 | + 'contest-contestant-no' => 'No', |
| 739 | + 'contest-contestant-yes' => 'Si', |
| 740 | + 'contest-contestant-commentcount' => 'Commentos', |
| 741 | + 'contest-contestant-overallrating' => 'Evalutation', |
| 742 | + 'contest-contestant-rating' => '$1 ($2 {{PLURAL:$2|voto|votos}})', |
| 743 | + 'contest-contestant-title' => 'Concurrente $1 ($2)', |
| 744 | + 'contest-contestant-header-id' => 'ID de concurrente', |
| 745 | + 'contest-contestant-header-contest' => 'Nomine de concurso', |
| 746 | + 'contest-contestant-header-challenge' => 'Nomine de defia', |
| 747 | + 'contest-contestant-header-submission' => 'Ligamine de submission', |
| 748 | + 'contest-contestant-header-country' => 'Pais de concurrente', |
| 749 | + 'contest-contestant-header-wmf' => 'Interesse in empleo a WMF', |
| 750 | + 'contest-contestant-header-volunteer' => 'Interesse in devenir voluntario', |
| 751 | + 'contest-contestant-header-rating' => 'Evalutation', |
| 752 | + 'contest-contestant-header-comments' => 'Numero de commentos', |
| 753 | + 'contest-contestant-submission-url' => 'Submission', |
| 754 | + 'contest-contestant-notsubmitted' => 'Non ancora submittite', |
| 755 | + 'contest-contestant-comments' => 'Commentos', |
| 756 | + 'contest-contestant-submit' => 'Salveguardar modificationes', |
| 757 | + 'contest-contestant-comment-by' => 'Commento per $1', |
| 758 | + 'contest-contestant-rate' => 'Evalutar iste concurrente', |
| 759 | + 'contest-contestant-not-voted' => 'Tu non ha ancora votate sur iste participante.', |
| 760 | + 'contest-contestant-voted' => 'Tu voto actual es $1.', |
| 761 | + 'contest-contestant-permalink' => 'Permaligamine', |
| 762 | + 'contest-email-signup-title' => 'Gratias pro acceptar le defia!', |
| 763 | + 'contest-email-reminder-title' => 'Solmente $1 {{PLURAL:$1|die|dies}} usque le fin del defia!', |
| 764 | + 'contest-mycontests-toplink' => 'Mi concursos', |
| 765 | + 'contest-mycontests-no-contests' => 'Tu non participa in un concurso.', |
| 766 | + 'contest-mycontests-active-header' => 'Concursos currente', |
| 767 | + 'contest-mycontests-finished-header' => 'Concursos passate', |
| 768 | + 'contest-mycontests-active-text' => 'Istes es le concursos in le quales tu participa actualmente.', |
| 769 | + 'contest-mycontests-finished-text' => 'Istes es le concursos passate in le quales tu ha participate.', |
| 770 | + 'contest-mycontests-header-contest' => 'Concurso', |
| 771 | + 'contest-mycontests-header-challenge' => 'Defia', |
| 772 | + 'contest-mycontests-signup-success' => 'Tu te ha inscribite pro iste concurso con successo.', |
| 773 | + 'contest-mycontests-addition-success' => 'Tu ha inviate le submission con successo! Gratias pro participar in iste concurso.', |
| 774 | + 'contest-mycontests-updated-success' => 'Tu ha modificate le submission con successo.', |
| 775 | + 'contest-mycontests-sessionfail' => 'Le submission non poteva esser salveguardate proque le datos del session es perdite. Per favor reproba.', |
| 776 | + 'contest-submission-submit' => 'Submitter', |
| 777 | + 'contest-submission-unknown' => 'Non existe un concurso con le nomine specificate.', |
| 778 | + 'contest-submission-header' => 'Gratias pro participar in iste concurso! Un vice que tu ha completate le defia, tu pote adder un ligamine a tu submission hic infra.', |
| 779 | + 'contest-submission-finished' => 'Iste concurso ha finite. Gratias pro haber participate!', |
| 780 | + 'contest-submission-submission' => 'Ligamine a tu submission', |
| 781 | + 'contest-submission-invalid-url' => 'Iste URL non corresponde a un del formatos permittite.', |
| 782 | + 'contest-submission-new-submission' => 'Tu debe ancora specificar le URL a tu submission. Isto debe esser facite ante le data limite.', |
| 783 | + 'contest-submission-current-submission' => 'Isto es le URL a tu submission, le qual tu pote modificar usque le data limite.', |
| 784 | + 'contest-submission-domains' => 'Le submissiones es limitate a iste sitos: $1', |
| 785 | +); |
| 786 | + |
| 787 | +/** Macedonian (Македонски) |
| 788 | + * @author Bjankuloski06 |
| 789 | + */ |
| 790 | +$messages['mk'] = array( |
| 791 | + 'contest-desc' => 'Додаток што им овозможува на корисниците да учествуваат во конкурси (задачи) приредени од администраторите. Жири комисијата има свој посредник за дискутирање и гласање за поднесеното.', |
| 792 | + 'contest-toplink' => 'Мои конкурси', |
| 793 | + 'right-contestadmin' => 'Раководење со конкурси', |
| 794 | + 'right-contestparticipant' => 'Учество во конкурси', |
| 795 | + 'right-contestjudge' => 'Оценување на учесници во конкурс', |
| 796 | + 'group-contestadmin' => 'Конкурсни администратори', |
| 797 | + 'group-contestadmin-member' => '{{GENDER:$1|конкурсен администратор}}', |
| 798 | + 'grouppage-contestadmin' => 'Project:Конкурсни_администратори', |
| 799 | + 'group-contestparticipant' => 'Учесници во конкурси', |
| 800 | + 'group-contestparticipant-member' => '{{GENDER:$1|учесник во конкурси}}', |
| 801 | + 'grouppage-contestparticipant' => 'Project:Учесници_во_конкурси', |
| 802 | + 'group-contestjudge' => 'Конкурсно жири', |
| 803 | + 'group-contestjudge-member' => '{{GENDER:$1|член на конкурсно жири}}', |
| 804 | + 'grouppage-contestjudge' => 'Project:Конкурсно_жири', |
| 805 | + 'prefs-contest' => 'Конкурси', |
| 806 | + 'contest-prefs-showtoplink' => 'Во менито најгоре прикажувај ја врската „[[Special:MyContests|Мои конкурси]]“.', |
| 807 | + 'contest-status-draft' => 'Нацрт (оневозможен)', |
| 808 | + 'contest-status-active' => 'Активен (овозможен)', |
| 809 | + 'contest-status-expired' => 'Истечен (овозможен, рокот изминал)', |
| 810 | + 'contest-status-finished' => 'Завршен (оневозможен)', |
| 811 | + 'special-contest' => 'Погл. на конкурс', |
| 812 | + 'special-contests' => 'Раководење со конкурси', |
| 813 | + 'special-contestsignup' => 'Пријава за конкурс', |
| 814 | + 'special-contestwelcome' => 'Погл. конкурс', |
| 815 | + 'special-editcontest' => 'Уреди конкурс', |
| 816 | + 'special-mycontests' => 'Мои конкурси', |
| 817 | + 'specialpages-group-contest' => 'Конкурси', |
| 818 | + 'contest-nav-contests' => 'Список на конкурси', |
| 819 | + 'contest-nav-editcontest' => 'Уреди конкурс', |
| 820 | + 'contest-nav-contest' => 'Опис и учесници', |
| 821 | + 'contest-nav-contestwelcome' => 'Целна страница', |
| 822 | + 'contest-nav-contestsignup' => 'Страница за пријавување', |
| 823 | + 'contest-special-addnew' => 'Додај нов конкурс', |
| 824 | + 'contest-special-namedoc' => 'Името на конкурсот е назнаката во URL-адресата, т.е. „име“ во Специјална:Конкурс/име', |
| 825 | + 'contest-special-newname' => 'Име на задачата', |
| 826 | + 'contest-special-add' => 'Додај конкурс', |
| 827 | + 'contest-special-existing' => 'Постоечки конкурси', |
| 828 | + 'contest-special-name' => 'Име', |
| 829 | + 'contest-special-status' => 'Статус', |
| 830 | + 'contest-special-submissioncount' => 'Поднесувања', |
| 831 | + 'contest-special-edit' => 'Уреди', |
| 832 | + 'contest-special-delete' => 'Избриши', |
| 833 | + 'contest-special-confirm-delete' => 'Дали сте сигурни дека сакате да го избришете конкурсов?', |
| 834 | + 'contest-special-delete-failed' => 'Не успеав да го избришам конкурсот.', |
| 835 | + 'editcontest-text' => 'Уредувате конкурс.', |
| 836 | + 'editcontest-legend' => 'Конкурс', |
| 837 | + 'contest-edit-name' => 'Име на конкурсот', |
| 838 | + 'contest-edit-status' => 'Статус на конкурсот', |
| 839 | + 'contest-edit-intro' => 'Воведна страница', |
| 840 | + 'contest-edit-opportunities' => 'Страница за можности', |
| 841 | + 'contest-edit-rulespage' => 'Страница со правила', |
| 842 | + 'contest-edit-help' => 'Страница за помош', |
| 843 | + 'contest-edit-signup' => 'Страниз на е-пошта за пријава', |
| 844 | + 'contest-edit-reminder' => 'Страница за потсетник на е-пошта', |
| 845 | + 'contest-edit-end' => 'Крај на конкурсот', |
| 846 | + 'contest-edit-exists-already' => 'Напомена: уредувате веќе постоечки конкурс - не создавате нов.', |
| 847 | + 'contest-edit-submit' => 'Поднеси', |
| 848 | + 'contest-edit-challenges' => 'Задачи на конкурсот', |
| 849 | + 'contest-edit-delete' => 'Избриши задача', |
| 850 | + 'contest-edit-add-first' => 'Додај задача', |
| 851 | + 'contest-edit-add-another' => 'одај друга задача', |
| 852 | + 'contest-edit-confirm-delete' => 'Дали сте сигурни дека сакате да го избришете конкурсов?', |
| 853 | + 'contest-edit-challenge-title' => 'Наслов на задачата', |
| 854 | + 'contest-edit-challenge-text' => 'Текст на задачата', |
| 855 | + 'contest-edit-challenge-oneline' => 'Краток опис', |
| 856 | + 'contest-welcome-unknown' => 'Не постои конкурс со наведеното име.', |
| 857 | + 'contest-welcome-rules' => 'За да учествувате, ќе треба да се согласите со', |
| 858 | + 'contest-welcome-rules-link' => 'правила на конкурсот', |
| 859 | + 'contest-welcome-signup' => 'Пријавете се сега', |
| 860 | + 'contest-welcome-js-off' => 'Корисничкиот посредник за конкурси користи JavaScript за подобрена работа. Или вашиот прелистувач не поддржува JavaScript, или ја имате исклучено.', |
| 861 | + 'contest-welcome-accept-challenge' => 'Задачата е прифатена', |
| 862 | + 'contest-welcome-select-header' => 'Одберете задача:', |
| 863 | + 'contest-signup-unknown' => 'Нема конкурс со наведеното име.', |
| 864 | + 'contest-signup-submit' => 'Пријава', |
| 865 | + 'contest-signup-header' => 'Пополнете го образецот за да се пријавите за $1.', |
| 866 | + 'contest-signup-email' => 'Вашата е-пошта', |
| 867 | + 'contest-signup-realname' => 'Вашето вистинско име', |
| 868 | + 'contest-signup-volunteer' => 'Сакам да бидам доброволец', |
| 869 | + 'contest-signup-wmf' => 'Сакам да работам во Фондацијата Викимедија', |
| 870 | + 'contest-signup-cv' => 'Врска до вашето резиме (CV)', |
| 871 | + 'contest-signup-readrules' => 'Потврдувам дека ги имам прочитано и се согласувам со [[$1|правилата на конкурсот]]', |
| 872 | + 'contest-signup-challenge' => 'Која задача ве бендисува?', |
| 873 | + 'contest-signup-finished' => 'Конкурсот заврши. Ви благодариме за учеството.', |
| 874 | + 'contest-signup-draft' => 'Конкурсот сè уште не е започнат. Бидете трпеливи.', |
| 875 | + 'contest-signup-country' => 'Вашата земја', |
| 876 | + 'contest-signup-require-rules' => 'Ќе треба да се согласите со правилата на конкурсот.', |
| 877 | + 'contest-signup-require-country' => 'Ќе треба да ја наведете земјата кајшто живеете.', |
| 878 | + 'contest-signup-invalid-email' => 'Наведенат е-пошта е неважечка.', |
| 879 | + 'contest-signup-invalid-name' => 'Наведеното име е прекратко.', |
| 880 | + 'contest-signup-require-challenge' => 'Мора да изберете задача.', |
| 881 | + 'contest-signup-invalid-cv' => 'Внесовте неважечка URL-адреса.', |
| 882 | + 'contest-contest-title' => 'Конкурс: $1', |
| 883 | + 'contest-contest-no-results' => 'Нема учесници за прикажување.', |
| 884 | + 'contest-contest-name' => 'Име', |
| 885 | + 'contest-contest-status' => 'Статус', |
| 886 | + 'contest-contest-submissioncount' => 'Бр. на учесници', |
| 887 | + 'contest-contest-contestants' => 'Учесници во конкурсот', |
| 888 | + 'contest-contest-contestants-text' => 'За да оцените некој учесник, стиснете на назнаката во левата колона.', |
| 889 | + 'contest-contestant-id' => 'Назнака', |
| 890 | + 'contest-contestant-challenge-name' => 'Име на задачата', |
| 891 | + 'contest-contestant-volunteer' => 'Доброволец', |
| 892 | + 'contest-contestant-wmf' => 'ФВМ', |
| 893 | + 'contest-contestant-no' => 'не', |
| 894 | + 'contest-contestant-yes' => 'да', |
| 895 | + 'contest-contestant-commentcount' => 'Забелешки', |
| 896 | + 'contest-contestant-overallrating' => 'Оценка', |
| 897 | + 'contest-contestant-rating' => '$1 ($2 {{PLURAL:$2|глас|гласа}})', |
| 898 | + 'contest-contestant-title' => 'Учесник $1 ($2)', |
| 899 | + 'contest-contestant-header-id' => 'Назнака на учесникот', |
| 900 | + 'contest-contestant-header-contest' => 'Име на учесникот', |
| 901 | + 'contest-contestant-header-challenge' => 'Име на задачата', |
| 902 | + 'contest-contestant-header-submission' => 'Врска на поднесеното', |
| 903 | + 'contest-contestant-header-country' => 'Земја на учесникот', |
| 904 | + 'contest-contestant-header-wmf' => 'Заинтересиран за работа во ФВМ', |
| 905 | + 'contest-contestant-header-volunteer' => 'Заинтересиран за доброволни активности', |
| 906 | + 'contest-contestant-header-rating' => 'Оценка', |
| 907 | + 'contest-contestant-header-comments' => 'Бр. на забелешки', |
| 908 | + 'contest-contestant-submission-url' => 'Поднесување', |
| 909 | + 'contest-contestant-notsubmitted' => 'Сè уште не е поднесено', |
| 910 | + 'contest-contestant-comments' => 'Забелешки', |
| 911 | + 'contest-contestant-submit' => 'Зачувај промени', |
| 912 | + 'contest-contestant-comment-by' => 'Забелешка од $1', |
| 913 | + 'contest-contestant-rate' => 'Оценување на учесник', |
| 914 | + 'contest-contestant-not-voted' => 'Сè уште го немате оценето овој учесник.', |
| 915 | + 'contest-contestant-voted' => 'Ваша тековна оценка: $1.', |
| 916 | + 'contest-contestant-permalink' => 'Постојана врска', |
| 917 | + 'contest-email-signup-title' => 'Ви благодариме за учеството во задачата!', |
| 918 | + 'contest-email-reminder-title' => 'Конкурсот трае уште само $1 {{PLURAL:$1|ден|дена}}!', |
| 919 | + 'contest-mycontests-toplink' => 'Мои конкурси', |
| 920 | + 'contest-mycontests-no-contests' => 'Не учествувате во ниеден конкурс.', |
| 921 | + 'contest-mycontests-active-header' => 'Тековни конкурси', |
| 922 | + 'contest-mycontests-finished-header' => 'Минати конкурси', |
| 923 | + 'contest-mycontests-active-text' => 'Моментално учествувате во наведениве конкурси.', |
| 924 | + 'contest-mycontests-finished-text' => 'Ова се конкурсите во кои имате учествувано.', |
| 925 | + 'contest-mycontests-header-contest' => 'Конкурс', |
| 926 | + 'contest-mycontests-header-challenge' => 'Задача', |
| 927 | + 'contest-mycontests-signup-success' => 'Успешно се пријавивте за овој конкурс.', |
| 928 | + 'contest-mycontests-addition-success' => 'Успешно поднесено! Ви благодариме за учеството.', |
| 929 | + 'contest-mycontests-updated-success' => 'Поднесеното е успешно изменето.', |
| 930 | + 'contest-mycontests-sessionfail' => 'Поднесеното не е зачувано поради загуба на сесиски податоци. Обидете се повторно.', |
| 931 | + 'contest-submission-submit' => 'Поднеси', |
| 932 | + 'contest-submission-unknown' => 'Нема конкурс со наведеното име.', |
| 933 | + 'contest-submission-header' => 'Ви благодариме што учествувавте на конкурсот! Штом ќе ја завршите задачата, при поднесувањето подолу можете да ставите и врска.', |
| 934 | + 'contest-submission-finished' => 'Конкурсот заврши. Ви благодариме за учеството!', |
| 935 | + 'contest-submission-submission' => 'Врска до поднесеното', |
| 936 | + 'contest-submission-invalid-url' => 'Оваа URL-адреса не одговара на допуштените формати.', |
| 937 | + 'contest-submission-new-submission' => 'Преостанува да наведете URL-адреса во поднесеното. Ова мора да го сторите пред истекот на рокот.', |
| 938 | + 'contest-submission-current-submission' => 'Ова е URL-адресата на поднесеното, кое можете да го менувате додека не истече рокот.', |
| 939 | + 'contest-submission-domains' => 'Поднесувањето е ограничено на следниве мрежни места: $1', |
| 940 | +); |
| 941 | + |
| 942 | +/** Dutch (Nederlands) |
| 943 | + * @author SPQRobin |
| 944 | + */ |
| 945 | +$messages['nl'] = array( |
| 946 | + 'contest-toplink' => 'Mijn wedstrijden', |
| 947 | + 'right-contestadmin' => 'Wedstrijden beheren', |
| 948 | + 'right-contestparticipant' => 'Deelnemen aan wedstrijden', |
| 949 | + 'group-contestadmin' => 'Wedstrijdbeheerders', |
| 950 | + 'group-contestadmin-member' => '{{GENDER:$1|wedstrijdbeheerder}}', |
| 951 | + 'grouppage-contestadmin' => 'Project:Wedstrijdbeheerders', |
| 952 | + 'group-contestparticipant' => 'Wedstrijddeelnemers', |
| 953 | + 'group-contestparticipant-member' => '{{GENDER:$1|wedstrijddeelnemer|wedstrijddeelneemster}}', |
| 954 | + 'grouppage-contestparticipant' => 'Project:Wedstrijddeelnemers', |
| 955 | + 'prefs-contest' => 'Wedstrijden', |
| 956 | + 'contest-prefs-showtoplink' => 'Een verwijzing naar [[Special:MyContests|mijn wedstrijden]] weergeven in het bovenste menu.', |
| 957 | + 'contest-status-draft' => 'Ontwerp (uitgeschakeld)', |
| 958 | + 'contest-status-active' => 'Actief (ingeschakeld)', |
| 959 | + 'contest-status-expired' => 'Verlopen (ingeschakeld, voorbij de einddatum)', |
| 960 | + 'contest-status-finished' => 'Gedaan (uitgeschakeld)', |
| 961 | + 'special-contest' => 'Een wedstrijd bekijken', |
| 962 | + 'special-contests' => 'Wedstrijden beheren', |
| 963 | + 'special-contestsignup' => 'Inschrijven voor een wedstrijd', |
| 964 | + 'special-contestwelcome' => 'Een wedstrijd bekijken', |
| 965 | + 'special-editcontest' => 'Een wedstrijd bewerken', |
| 966 | + 'special-mycontests' => 'Mijn wedstrijden', |
| 967 | + 'specialpages-group-contest' => 'Wedstrijden', |
| 968 | + 'contest-nav-contests' => 'Lijst van wedstrijden', |
| 969 | + 'contest-nav-editcontest' => 'Wedstrijd bewerken', |
| 970 | + 'contest-nav-contest' => 'Samenvatting en deelnemers', |
| 971 | + 'contest-special-addnew' => 'Een nieuwe wedstrijd toevoegen', |
| 972 | + 'contest-special-newname' => 'Wedstrijdnaam', |
| 973 | + 'contest-special-add' => 'Wedstrijd toevoegen', |
| 974 | + 'contest-special-existing' => 'Bestaande wedstrijden', |
| 975 | + 'contest-special-name' => 'Naam', |
| 976 | + 'contest-special-status' => 'Status', |
| 977 | + 'contest-special-submissioncount' => 'Aantal inzendingen', |
| 978 | + 'contest-special-edit' => 'Bewerken', |
| 979 | + 'contest-special-delete' => 'Verwijderen', |
| 980 | + 'contest-special-confirm-delete' => 'Weet u zeker dat u deze wedstrijd wilt verwijderen?', |
| 981 | + 'contest-special-delete-failed' => 'Fout bij het verwijderen van de wedstrijd.', |
| 982 | + 'editcontest-text' => 'U bent een wedstrijd aan het bewerken.', |
| 983 | + 'editcontest-legend' => 'Wedstrijd', |
| 984 | + 'contest-edit-name' => 'Wedstrijdnaam', |
| 985 | + 'contest-edit-status' => 'Status van de wedstrijd', |
| 986 | + 'contest-edit-end' => 'Einde van wedstrijd', |
| 987 | + 'contest-edit-exists-already' => 'Opmerking: u bent geen nieuwe wedstrijd aan het maken, maar een bestaande aan het bewerken.', |
| 988 | + 'contest-edit-submit' => 'Opslaan', |
| 989 | + 'contest-edit-challenges' => 'Wedstrijduitdagingen', |
| 990 | + 'contest-edit-delete' => 'Uitdaging verwijderen', |
| 991 | + 'contest-edit-add-first' => 'Een uitdaging toevoegen', |
| 992 | + 'contest-edit-add-another' => 'Een andere uitdaging toevoegen', |
| 993 | + 'contest-edit-confirm-delete' => 'Weet u zeker dat u deze uitdaging wilt verwijderen?', |
| 994 | + 'contest-edit-challenge-title' => 'Uitdagingstitel', |
| 995 | + 'contest-edit-challenge-text' => 'Uitdagingstekst', |
| 996 | + 'contest-edit-challenge-oneline' => 'Korte beschrijving', |
| 997 | + 'contest-welcome-unknown' => 'Er is geen wedstrijd met de opgegeven naam.', |
| 998 | + 'contest-welcome-rules' => 'Om te kunnen deelnemen bent u verplicht akkoord te gaan met', |
| 999 | + 'contest-welcome-rules-link' => 'de wedstrijdregels', |
| 1000 | + 'contest-welcome-signup' => 'Nu inschrijven', |
| 1001 | + 'contest-welcome-accept-challenge' => 'Uitdaging aanvaard', |
| 1002 | + 'contest-welcome-select-header' => 'Selecteer uw uitdaging:', |
| 1003 | + 'contest-signup-unknown' => 'Er is geen wedstrijd met de opgegeven naam.', |
| 1004 | + 'contest-signup-submit' => 'Inschrijven', |
| 1005 | + 'contest-signup-header' => 'Vul het formulier in om uw inschrijving voor $1 te voltooien.', |
| 1006 | + 'contest-signup-email' => 'Uw e-mailadres', |
| 1007 | + 'contest-signup-realname' => 'Uw echte naam', |
| 1008 | + 'contest-signup-cv' => 'Verwijzing naar uw CV', |
| 1009 | + 'contest-signup-readrules' => 'Ik bevestig dat ik [[$1|de wedstrijdregels]] heb gelezen en ermee akkoord ga', |
| 1010 | + 'contest-signup-challenge' => 'Welke uitdaging wilt u aangaan?', |
| 1011 | + 'contest-signup-finished' => 'Deze wedstrijd is afgelopen. Bedankt voor uw deelname!', |
| 1012 | + 'contest-signup-draft' => 'Deze wedstrijd is nog niet begonnen.', |
| 1013 | + 'contest-signup-country' => 'Uw land', |
| 1014 | + 'contest-signup-require-rules' => 'U moet akkoord gaan met de wedstrijdregels.', |
| 1015 | + 'contest-signup-require-country' => 'U moet het land waarin u verblijft opgeven.', |
| 1016 | + 'contest-signup-invalid-email' => 'Het e-mailadres dat u hebt opgegeven is niet geldig.', |
| 1017 | + 'contest-signup-invalid-name' => 'De naam die u hebt opgegeven is te kort.', |
| 1018 | + 'contest-signup-require-challenge' => 'U moet een uitdaging selecteren.', |
| 1019 | + 'contest-signup-invalid-cv' => 'U hebt een ongeldige URL ingevoerd.', |
| 1020 | + 'contest-contest-title' => 'Wedstrijd: $1', |
| 1021 | + 'contest-contest-name' => 'Naam', |
| 1022 | + 'contest-contest-status' => 'Status', |
| 1023 | + 'contest-contest-submissioncount' => 'Aantal deelnemers', |
| 1024 | + 'contest-contestant-challenge-name' => 'Naam van de uitdaging', |
| 1025 | + 'contest-contestant-volunteer' => 'Vrijwilliger', |
| 1026 | + 'contest-contestant-wmf' => 'WMF', |
| 1027 | + 'contest-contestant-no' => 'Nee', |
| 1028 | + 'contest-contestant-yes' => 'Ja', |
| 1029 | + 'contest-contestant-commentcount' => 'Opmerkingen', |
| 1030 | + 'contest-contestant-overallrating' => 'Waardering', |
| 1031 | + 'contest-contestant-rating' => '$1 ($2 {{PLURAL:$2|stem|stemmen}})', |
| 1032 | + 'contest-contestant-header-contest' => 'Wedstrijdnaam', |
| 1033 | + 'contest-contestant-header-challenge' => 'Naam van de uitdaging', |
| 1034 | + 'contest-contestant-header-wmf' => 'Geïnteresseerd in een job bij WMF', |
| 1035 | + 'contest-contestant-header-volunteer' => 'Geïnteresseerd in kansen als vrijwilliger', |
| 1036 | + 'contest-contestant-header-rating' => 'Waardering', |
| 1037 | + 'contest-contestant-header-comments' => 'Aantal opmerkingen', |
| 1038 | + 'contest-contestant-submission-url' => 'Inzending', |
| 1039 | + 'contest-contestant-notsubmitted' => 'Nog niet ingediend', |
| 1040 | + 'contest-contestant-comments' => 'Opmerkingen', |
| 1041 | + 'contest-contestant-submit' => 'Wijzigingen opslaan', |
| 1042 | + 'contest-contestant-comment-by' => 'Opmerking van $1', |
| 1043 | + 'contest-contestant-not-voted' => 'U hebt nog niet gestemd op deze deelnemer.', |
| 1044 | + 'contest-contestant-voted' => 'Uw huidige stem is $1.', |
| 1045 | + 'contest-email-reminder-title' => 'Nog maar $1 {{PLURAL:$1|dag|dagen}} tot het einde van deze uitdaging!', |
| 1046 | + 'contest-mycontests-toplink' => 'Mijn wedstrijden', |
| 1047 | + 'contest-mycontests-no-contests' => 'U neemt niet deel aan een wedstrijd.', |
| 1048 | + 'contest-mycontests-active-header' => 'Lopende wedstrijden', |
| 1049 | + 'contest-mycontests-active-text' => 'Dit zijn de wedstrijden waar u momenteel aan deelneemt.', |
| 1050 | + 'contest-mycontests-header-contest' => 'Wedstrijd', |
| 1051 | + 'contest-mycontests-header-challenge' => 'Uitdaging', |
| 1052 | + 'contest-mycontests-signup-success' => 'U bent ingeschreven voor deze wedstrijd.', |
| 1053 | + 'contest-mycontests-addition-success' => 'Uw inzending is ingediend! Bedankt voor uw deelname aan deze wedstrijd.', |
| 1054 | + 'contest-mycontests-updated-success' => 'Uw inzending is gewijzigd.', |
| 1055 | + 'contest-submission-submit' => 'Opslaan', |
| 1056 | + 'contest-submission-unknown' => 'Er is geen wedstrijd met de opgegeven naam.', |
| 1057 | + 'contest-submission-finished' => 'Deze wedstrijd is afgelopen. Bedankt voor uw deelname!', |
| 1058 | + 'contest-submission-submission' => 'Verwijzing naar uw inzending', |
| 1059 | +); |
| 1060 | + |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/Contest.i18n.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 1061 | + native |
Index: branches/wmf/1.18wmf1/extensions/Contest/Contest.hooks.php |
— | — | @@ -0,0 +1,185 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Static class for hooks handled by the Contest extension. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file Contest.hooks.php |
| 10 | + * @ingroup Contest |
| 11 | + * |
| 12 | + * @licence GNU GPL v3+ |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +final class ContestHooks { |
| 16 | + |
| 17 | + /** |
| 18 | + * Schema update to set up the needed database tables. |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + * |
| 22 | + * @param DatabaseUpdater $updater |
| 23 | + * |
| 24 | + * @return true |
| 25 | + */ |
| 26 | + public static function onSchemaUpdate( /* DatabaseUpdater */ $updater = null ) { |
| 27 | + $updater->addExtensionUpdate( array( |
| 28 | + 'addTable', |
| 29 | + 'contests', |
| 30 | + dirname( __FILE__ ) . '/Contest.sql', |
| 31 | + true |
| 32 | + ) ); |
| 33 | + |
| 34 | + $updater->addExtensionUpdate( array( |
| 35 | + 'addField', |
| 36 | + 'contests', |
| 37 | + 'contest_signup_email', |
| 38 | + dirname( __FILE__ ) . '/sql/AddContestEmailFields.sql', |
| 39 | + true |
| 40 | + ) ); |
| 41 | + |
| 42 | + return true; |
| 43 | + } |
| 44 | + |
| 45 | + /** |
| 46 | + * Hook to add PHPUnit test cases. |
| 47 | + * |
| 48 | + * @since 0.1 |
| 49 | + * |
| 50 | + * @param array $files |
| 51 | + * |
| 52 | + * @return true |
| 53 | + */ |
| 54 | + public static function registerUnitTests( array &$files ) { |
| 55 | + $testDir = dirname( __FILE__ ) . '/test/'; |
| 56 | + |
| 57 | + $files[] = $testDir . 'ContestValidationTests.php'; |
| 58 | + |
| 59 | + return true; |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Called when changing user email address. |
| 64 | + * @see https://www.mediawiki.org/wiki/Manual:Hooks/UserSetEmail |
| 65 | + * |
| 66 | + * Checks if there are any active contests in which the user is participating, |
| 67 | + * and if so, updates the email there as well. |
| 68 | + * |
| 69 | + * @since 0.1 |
| 70 | + * |
| 71 | + * @param User $user |
| 72 | + * @param string $email |
| 73 | + * |
| 74 | + * @return true |
| 75 | + */ |
| 76 | + public static function onUserSetEmail( User $user, &$email ) { |
| 77 | + $dbr = wfGetDB( DB_SLAVE ); |
| 78 | + |
| 79 | + $contestants = $dbr->select( |
| 80 | + array( 'contest_contestants', 'contests' ), |
| 81 | + array( 'contestant_id' ), |
| 82 | + array( 'contest_status' => Contest::STATUS_ACTIVE, 'contestant_user_id' => $user->getId() ), |
| 83 | + __METHOD__, |
| 84 | + array(), |
| 85 | + array( 'contests' => array( 'INNER JOIN', array( 'contest_id=contestant_contest_id' ) ) ) |
| 86 | + ); |
| 87 | + |
| 88 | + $contestantIds = array(); |
| 89 | + |
| 90 | + foreach ( $contestants as $contestant ) { |
| 91 | + $contestantIds[] = $contestant->contestant_id; |
| 92 | + } |
| 93 | + |
| 94 | + if ( count( $contestantIds ) > 0 ) { |
| 95 | + ContestContestant::s()->update( |
| 96 | + array( 'email' => $email ), |
| 97 | + array( 'id' => $contestantIds ) |
| 98 | + ); |
| 99 | + } |
| 100 | + |
| 101 | + return true; |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Called after the personal URLs have been set up, before they are shown. |
| 106 | + * @see https://www.mediawiki.org/wiki/Manual:Hooks/PersonalUrls |
| 107 | + * |
| 108 | + * @since 0.1 |
| 109 | + * |
| 110 | + * @param array $personal_urls |
| 111 | + * @param Title $title |
| 112 | + * |
| 113 | + * @return true |
| 114 | + */ |
| 115 | + public static function onPersonalUrls( array &$personal_urls, Title &$title ) { |
| 116 | + if ( ContestSettings::get( 'enableTopLink' ) ) { |
| 117 | + global $wgUser; |
| 118 | + |
| 119 | + // Find the watchlist item and replace it by the my contests link and itself. |
| 120 | + if ( $wgUser->isLoggedIn() && $wgUser->getOption( 'contest_showtoplink' ) ) { |
| 121 | + $keys = array_keys( $personal_urls ); |
| 122 | + $watchListLocation = array_search( 'watchlist', $keys ); |
| 123 | + $watchListItem = $personal_urls[$keys[$watchListLocation]]; |
| 124 | + |
| 125 | + $url = SpecialPage::getTitleFor( 'MyContests' )->getLinkUrl(); |
| 126 | + $myContests = array( |
| 127 | + 'text' => wfMsg( 'contest-toplink' ), |
| 128 | + 'href' => $url, |
| 129 | + 'active' => ( $url == $title->getLinkUrl() ) |
| 130 | + ); |
| 131 | + |
| 132 | + array_splice( $personal_urls, $watchListLocation, 1, array( $myContests, $watchListItem ) ); |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + return true; |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * Adds the preferences of Contest to the list of available ones. |
| 141 | + * @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences |
| 142 | + * |
| 143 | + * @since 0.1 |
| 144 | + * |
| 145 | + * @param User $user |
| 146 | + * @param array $preferences |
| 147 | + * |
| 148 | + * @return true |
| 149 | + */ |
| 150 | + public static function onGetPreferences( User $user, array &$preferences ) { |
| 151 | + if ( ContestSettings::get( 'enableTopLink' ) ) { |
| 152 | + $preferences['contest_showtoplink'] = array( |
| 153 | + 'type' => 'toggle', |
| 154 | + 'label-message' => 'contest-prefs-showtoplink', |
| 155 | + 'section' => 'contest', |
| 156 | + ); |
| 157 | + } |
| 158 | + |
| 159 | + return true; |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * Used when generating internal and interwiki links in Linker::link(), |
| 164 | + * just before the function returns a value. |
| 165 | + * @see https://www.mediawiki.org/wiki/Manual:Hooks/LinkEnd |
| 166 | + * |
| 167 | + * @since 0.1 |
| 168 | + * |
| 169 | + * @param $skin |
| 170 | + * @param Title $target |
| 171 | + * @param array $options |
| 172 | + * @param string $text |
| 173 | + * @param array $attribs |
| 174 | + * @param $ret |
| 175 | + * |
| 176 | + * @return true |
| 177 | + */ |
| 178 | + public static function onLinkEnd( $skin, Title $target, array $options, &$text, array &$attribs, &$ret ) { |
| 179 | + if ( $GLOBALS['wgContestEmailParse'] ) { |
| 180 | + $attribs['href'] = $target->getFullURL(); |
| 181 | + } |
| 182 | + |
| 183 | + return true; |
| 184 | + } |
| 185 | + |
| 186 | +} |
Property changes on: branches/wmf/1.18wmf1/extensions/Contest/Contest.hooks.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 187 | + native |