-

- html( 'title' ) ?> -

+

html( 'title' ) ?>

html( 'bodytext' ) ?> diff --git a/includes/skins/SkinTemplate.php b/includes/skins/SkinTemplate.php index 64ad816ac1..c1db302ded 100644 --- a/includes/skins/SkinTemplate.php +++ b/includes/skins/SkinTemplate.php @@ -261,7 +261,7 @@ class SkinTemplate extends Skin { protected function prepareQuickTemplate() { global $wgContLang, $wgScript, $wgStylePath, $wgMimeType, $wgJsMimeType, $wgDisableCounters, $wgSitename, $wgLogo, $wgMaxCredits, - $wgShowCreditsIfMax, $wgPageShowWatchingUsers, $wgArticlePath, + $wgShowCreditsIfMax, $wgArticlePath, $wgScriptPath, $wgServer; wfProfileIn( __METHOD__ ); @@ -386,19 +386,6 @@ class SkinTemplate extends Skin { } } - if ( $wgPageShowWatchingUsers ) { - $dbr = wfGetDB( DB_SLAVE ); - $num = $dbr->selectField( 'watchlist', 'COUNT(*)', - array( 'wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace() ), - __METHOD__ - ); - if ( $num > 0 ) { - $tpl->set( 'numberofwatchingusers', - $this->msg( 'number_of_watching_users_pageview' )->numParams( $num )->parse() - ); - } - } - if ( $wgMaxCredits != 0 ) { $tpl->set( 'credits', Action::factory( 'credits', $this->getWikiPage(), $this->getContext() )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) ); diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php index 3b20161187..febd1194a8 100644 --- a/includes/specialpage/SpecialPageFactory.php +++ b/includes/specialpage/SpecialPageFactory.php @@ -47,7 +47,7 @@ class SpecialPageFactory { /** * List of special page names to the subclass of SpecialPage which handles them. */ - private static $list = array( + private static $coreList = array( // Maintenance Reports 'BrokenRedirects' => 'BrokenRedirectsPage', 'Deadendpages' => 'DeadendPagesPage', @@ -59,7 +59,7 @@ class SpecialPageFactory { 'Withoutinterwiki' => 'WithoutInterwikiPage', 'Protectedpages' => 'SpecialProtectedpages', 'Protectedtitles' => 'SpecialProtectedtitles', - 'Shortpages' => 'ShortpagesPage', + 'Shortpages' => 'ShortPagesPage', 'Uncategorizedcategories' => 'UncategorizedCategoriesPage', 'Uncategorizedimages' => 'UncategorizedImagesPage', 'Uncategorizedpages' => 'UncategorizedPagesPage', @@ -74,7 +74,7 @@ class SpecialPageFactory { 'Wantedtemplates' => 'WantedTemplatesPage', // List of pages - 'Allpages' => 'SpecialAllpages', + 'Allpages' => 'SpecialAllPages', 'Prefixindex' => 'SpecialPrefixindex', 'Categories' => 'SpecialCategories', 'Listredirects' => 'ListredirectsPage', @@ -123,7 +123,7 @@ class SpecialPageFactory { // Data and tools 'Statistics' => 'SpecialStatistics', - 'Allmessages' => 'SpecialAllmessages', + 'Allmessages' => 'SpecialAllMessages', 'Version' => 'SpecialVersion', 'Lockdb' => 'SpecialLockdb', 'Unlockdb' => 'SpecialUnlockdb', @@ -175,6 +175,7 @@ class SpecialPageFactory { 'Userlogout' => 'SpecialUserlogout', ); + private static $list; private static $aliases; /** @@ -218,9 +219,11 @@ class SpecialPageFactory { global $wgEnableEmail, $wgEnableJavaScriptTest; global $wgPageLanguageUseDB; - if ( !is_object( self::$list ) ) { + if ( !is_array( self::$list ) ) { wfProfileIn( __METHOD__ ); + self::$list = self::$coreList; + if ( !$wgDisableCounters ) { self::$list['Popularpages'] = 'PopularPagesPage'; } @@ -263,32 +266,55 @@ class SpecialPageFactory { /** * Initialise and return the list of special page aliases. Returns an object with - * properties which can be accessed $obj->pagename - each property is an array of - * aliases; the first in the array is the canonical alias. All registered special - * pages are guaranteed to have a property entry, and for that property array to - * contain at least one entry (English fallbacks will be added if necessary). + * properties which can be accessed $obj->pagename - each property name is an + * alias, with the value being the canonical name of the special page. All + * registered special pages are guaranteed to map to themselves. * @return object */ private static function getAliasListObject() { if ( !is_object( self::$aliases ) ) { global $wgContLang; $aliases = $wgContLang->getSpecialPageAliases(); - - $missingPages = self::getPageList(); + $pageList = self::getPageList(); self::$aliases = array(); + $keepAlias = array(); + + // Force every canonical name to be an alias for itself. + foreach ( $pageList as $name => $stuff ) { + $caseFoldedAlias = $wgContLang->caseFold( $name ); + self::$aliases[$caseFoldedAlias] = $name; + $keepAlias[$caseFoldedAlias] = 'canonical'; + } + // Check for $aliases being an array since Language::getSpecialPageAliases can return null if ( is_array( $aliases ) ) { foreach ( $aliases as $realName => $aliasList ) { - foreach ( $aliasList as $alias ) { - self::$aliases[$wgContLang->caseFold( $alias )] = $realName; + $aliasList = array_values( $aliasList ); + foreach ( $aliasList as $i => $alias ) { + $caseFoldedAlias = $wgContLang->caseFold( $alias ); + + if ( isset( self::$aliases[$caseFoldedAlias] ) && + $realName === self::$aliases[$caseFoldedAlias] + ) { + // Ignore same-realName conflicts + continue; + } + + if ( !isset( $keepAlias[$caseFoldedAlias] ) ) { + self::$aliases[$caseFoldedAlias] = $realName; + if ( !$i ) { + $keepAlias[$caseFoldedAlias] = 'first'; + } + } elseif ( !$i ) { + wfWarn( "First alias '$alias' for $realName conflicts with " . + "{$keepAlias[$caseFoldedAlias]} alias for " . + self::$aliases[$caseFoldedAlias] + ); + } } - unset( $missingPages->$realName ); } } - foreach ( $missingPages as $name => $stuff ) { - self::$aliases[$wgContLang->caseFold( $name )] = $name; - } // Cast to object: func()[$key] doesn't work, but func()->$key does self::$aliases = (object)self::$aliases; @@ -381,12 +407,12 @@ class SpecialPageFactory { if ( isset( $specialPageList[$realName] ) ) { $rec = $specialPageList[$realName]; - if ( is_string( $rec ) ) { - $className = $rec; - $page = new $className; - } elseif ( is_callable( $rec ) ) { + if ( is_callable( $rec ) ) { // Use callback to instantiate the special page $page = call_user_func( $rec ); + } elseif ( is_string( $rec ) ) { + $className = $rec; + $page = new $className; } elseif ( is_array( $rec ) ) { $className = array_shift( $rec ); // @deprecated, officially since 1.18, unofficially since forever @@ -621,29 +647,42 @@ class SpecialPageFactory { public static function getLocalNameFor( $name, $subpage = false ) { global $wgContLang; $aliases = $wgContLang->getSpecialPageAliases(); + $aliasList = self::getAliasListObject(); - if ( isset( $aliases[$name][0] ) ) { - $name = $aliases[$name][0]; - } else { - // Try harder in case someone misspelled the correct casing + // Find the first alias that maps back to $name + if ( isset( $aliases[$name] ) ) { $found = false; - // Check for $aliases being an array since Language::getSpecialPageAliases can return null + foreach ( $aliases[$name] as $alias ) { + $caseFoldedAlias = $wgContLang->caseFold( $alias ); + $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias ); + if ( isset( $aliasList->$caseFoldedAlias ) && + $aliasList->$caseFoldedAlias === $name + ) { + $name = $alias; + $found = true; + break; + } + } + if ( !$found ) { + wfWarn( "Did not find a usable alias for special page '$name'. " . + "It seems all defined aliases conflict?" ); + } + } else { + // Check if someone misspelled the correct casing if ( is_array( $aliases ) ) { foreach ( $aliases as $n => $values ) { if ( strcasecmp( $name, $n ) === 0 ) { wfWarn( "Found alias defined for $n when searching for " . "special page aliases for $name. Case mismatch?" ); - $name = $values[0]; - $found = true; - break; + return self::getLocalNameFor( $n, $subpage ); } } } - if ( !$found ) { - wfWarn( "Did not find alias for special page '$name'. " . - "Perhaps no aliases are defined for it?" ); - } + + wfWarn( "Did not find alias for special page '$name'. " . + "Perhaps no aliases are defined for it?" ); } + if ( $subpage !== false && !is_null( $subpage ) ) { $name = "$name/$subpage"; } diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php index 07e1be1933..6219fc48d2 100644 --- a/includes/specials/SpecialActiveusers.php +++ b/includes/specials/SpecialActiveusers.php @@ -259,7 +259,7 @@ class SpecialActiveUsers extends SpecialPage { array( 'activeusers-intro', $this->getLanguage()->formatNum( $days ) ) ); // Occasionally merge in new updates - $seconds = min( self::mergeActiveUsers( 600, $days ), $days * 86400 ); + $seconds = min( self::mergeActiveUsers( 300, $days ), $days * 86400 ); // Mention the level of staleness $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl', $this->getLanguage()->formatDuration( $seconds ) ); @@ -339,21 +339,19 @@ class SpecialActiveUsers extends SpecialPage { return false; // exclusive update (avoids duplicate entries) } - $now = time(); + $nowUnix = time(); + // Get the last-updated timestamp for the cache $cTime = $dbw->selectField( 'querycache_info', 'qci_timestamp', array( 'qci_type' => 'activeusers' ) ); $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1; - // If a transaction was already started, it might have an old - // snapshot, so kludge the timestamp range a few seconds back. - $cTimeUnix -= 5; // Pick the date range to fetch from. This is normally from the last // update to till the present time, but has a limited window for sanity. // If the window is limited, multiple runs are need to fully populate it. - $sTimestamp = max( $cTimeUnix, $now - $days * 86400 ); - $eTimestamp = min( $sTimestamp + $window, $now ); + $sTimestamp = max( $cTimeUnix, $nowUnix - $days * 86400 ); + $eTimestamp = min( $sTimestamp + $window, $nowUnix ); // Get all the users active since the last update $res = $dbw->select( @@ -381,7 +379,7 @@ class SpecialActiveUsers extends SpecialPage { $dbw->delete( 'querycachetwo', array( 'qcc_type' => 'activeusers', - 'qcc_value < ' . $dbw->addQuotes( $now - $days * 86400 ) // TS_UNIX + 'qcc_value < ' . $dbw->addQuotes( $nowUnix - $days * 86400 ) // TS_UNIX ), __METHOD__ ); @@ -425,11 +423,15 @@ class SpecialActiveUsers extends SpecialPage { } } + // If a transaction was already started, it might have an old + // snapshot, so kludge the timestamp range back as needed. + $asOfTimestamp = min( $eTimestamp, (int)$dbw->trxTimestamp() ); + // Touch the data freshness timestamp $dbw->replace( 'querycache_info', array( 'qci_type' ), array( 'qci_type' => 'activeusers', - 'qci_timestamp' => $dbw->timestamp( $eTimestamp ) ), // not always $now + 'qci_timestamp' => $dbw->timestamp( $asOfTimestamp ) ), // not always $now __METHOD__ ); diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php index 3297c17a6b..cf82b869ca 100644 --- a/includes/specials/SpecialBlock.php +++ b/includes/specials/SpecialBlock.php @@ -98,6 +98,7 @@ class SpecialBlock extends FormSpecialPage { $form->setWrapperLegendMsg( 'blockip-legend' ); $form->setHeaderText( '' ); $form->setSubmitCallback( array( __CLASS__, 'processUIForm' ) ); + $form->setSubmitDestructive(); $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit'; $form->setSubmitTextMsg( $msg ); diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php index e6750e125e..d8eec7d9d8 100644 --- a/includes/specials/SpecialBooksources.php +++ b/includes/specials/SpecialBooksources.php @@ -26,7 +26,6 @@ * The parser creates links to this page when dealing with ISBNs in wikitext * * @author Rob Church - * @todo Validate ISBNs using the standard check-digit method * @ingroup SpecialPage */ class SpecialBookSources extends SpecialPage { @@ -73,7 +72,9 @@ class SpecialBookSources extends SpecialPage { $sum = 0; if ( strlen( $isbn ) == 13 ) { for ( $i = 0; $i < 12; $i++ ) { - if ( $i % 2 == 0 ) { + if ( $isbn[$i] === 'X' ) { + return false; + } elseif ( $i % 2 == 0 ) { $sum += $isbn[$i]; } else { $sum += 3 * $isbn[$i]; @@ -81,11 +82,14 @@ class SpecialBookSources extends SpecialPage { } $check = ( 10 - ( $sum % 10 ) ) % 10; - if ( $check == $isbn[12] ) { + if ( (string)$check === $isbn[12] ) { return true; } } elseif ( strlen( $isbn ) == 10 ) { for ( $i = 0; $i < 9; $i++ ) { + if ( $isbn[$i] === 'X' ) { + return false; + } $sum += $isbn[$i] * ( $i + 1 ); } @@ -93,7 +97,7 @@ class SpecialBookSources extends SpecialPage { if ( $check == 10 ) { $check = "X"; } - if ( $check == $isbn[9] ) { + if ( (string)$check === $isbn[9] ) { return true; } } @@ -134,16 +138,10 @@ class SpecialBookSources extends SpecialPage { array( 'autofocus' => true, 'class' => 'mw-ui-input-inline' ) ); - if ( $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { - $form .= ' ' . Xml::submitButton( - $this->msg( 'booksources-search' )->text(), - array( 'class' => 'mw-ui-button mw-ui-progressive' ) - ) . "

\n"; - } else { - $form .= ' ' . Xml::submitButton( - $this->msg( 'booksources-search' )->text() - ) . "

\n"; - } + $form .= ' ' . Html::submitButton( + $this->msg( 'booksources-search' )->text(), + array(), array( 'mw-ui-progressive' ) + ) . "

\n"; $form .= Html::closeElement( 'form' ) . "\n"; $form .= Html::closeElement( 'fieldset' ) . "\n"; diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php index 95f9efd256..3a13b7ed60 100644 --- a/includes/specials/SpecialCategories.php +++ b/includes/specials/SpecialCategories.php @@ -188,9 +188,11 @@ class CategoryPager extends AlphabeticPager { $this->msg( 'categories' )->text(), Xml::inputLabel( $this->msg( 'categoriesfrom' )->text(), - 'from', 'from', 20, $from ) . + 'from', 'from', 20, $from, array( 'class' => 'mw-ui-input-inline' ) ) . ' ' . - Xml::submitButton( $this->msg( 'allpagessubmit' )->text() + Html::submitButton( + $this->msg( 'allpagessubmit' )->text(), + array(), array( 'mw-ui-progressive' ) ) ) ); diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php index e0be838b81..12bbd2a7ce 100644 --- a/includes/specials/SpecialChangeEmail.php +++ b/includes/specials/SpecialChangeEmail.php @@ -112,16 +112,12 @@ class SpecialChangeEmail extends FormSpecialPage { $form->setTableId( 'mw-changeemail-table' ); $form->setWrapperLegend( false ); $form->setSubmitTextMsg( 'changeemail-submit' ); - $form->addHiddenField( 'returnto', $this->getRequest()->getVal( 'returnto' ) ); + $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) ); } public function onSubmit( array $data ) { - if ( $this->getRequest()->getBool( 'wpCancel' ) ) { - $status = Status::newGood( true ); - } else { - $password = isset( $data['Password'] ) ? $data['Password'] : null; - $status = $this->attemptChange( $this->getUser(), $password, $data['NewEmail'] ); - } + $password = isset( $data['Password'] ) ? $data['Password'] : null; + $status = $this->attemptChange( $this->getUser(), $password, $data['NewEmail'] ); $this->status = $status; @@ -129,18 +125,21 @@ class SpecialChangeEmail extends FormSpecialPage { } public function onSuccess() { - $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) ); + $request = $this->getRequest(); + + $titleObj = Title::newFromText( $request->getVal( 'returnto' ) ); if ( !$titleObj instanceof Title ) { $titleObj = Title::newMainPage(); } + $query = $request->getVal( 'returntoquery' ); if ( $this->status->value === true ) { - $this->getOutput()->redirect( $titleObj->getFullURL() ); + $this->getOutput()->redirect( $titleObj->getFullURL( $query ) ); } elseif ( $this->status->value === 'eauth' ) { # Notify user that a confirmation email has been sent... $this->getOutput()->wrapWikiMsg( "
\n$1\n
", 'eauthentsent', $this->getUser()->getName() ); - $this->getOutput()->addReturnTo( $titleObj ); // just show the link to go back + $this->getOutput()->addReturnTo( $titleObj, wfCgiToArray( $query ) ); // just show the link to go back } } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index 32a887c407..5631c3aff4 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -478,13 +478,8 @@ class SpecialContributions extends IncludableSpecialPage { if ( $tagFilter ) { $filterSelection = Html::rawElement( 'td', - array( 'class' => 'mw-label' ), - array_shift( $tagFilter ) - ); - $filterSelection .= Html::rawElement( - 'td', - array( 'class' => 'mw-input' ), - implode( ' ', $tagFilter ) + array(), + array_shift( $tagFilter ) . implode( ' ', $tagFilter ) ); } else { $filterSelection = Html::rawElement( 'td', array( 'colspan' => 2 ), '' ); @@ -510,7 +505,7 @@ class SpecialContributions extends IncludableSpecialPage { 'target', $this->opts['target'], 'text', - array( 'size' => '40', 'required' => '', 'class' => 'mw-input' ) + + array( 'size' => '40', 'required' => '', 'class' => 'mw-input mw-ui-input-inline' ) + ( $this->opts['target'] ? array() : array( 'autofocus' ) ) ); @@ -522,16 +517,12 @@ class SpecialContributions extends IncludableSpecialPage { $namespaceSelection = Xml::tags( 'td', - array( 'class' => 'mw-label' ), + array(), Xml::label( $this->msg( 'namespace' )->text(), 'namespace', '' - ) - ); - $namespaceSelection .= Html::rawElement( - 'td', - null, + ) . Html::namespaceSelector( array( 'selected' => $this->opts['namespace'], 'all' => '' ), array( @@ -617,9 +608,9 @@ class SpecialContributions extends IncludableSpecialPage { $this->opts['year'] === '' ? MWTimestamp::getInstance()->format( 'Y' ) : $this->opts['year'], $this->opts['month'] ) . ' ' . - Xml::submitButton( + Html::submitButton( $this->msg( 'sp-contributions-submit' )->text(), - array( 'class' => 'mw-submit' ) + array( 'class' => 'mw-submit' ), array( 'mw-ui-progressive' ) ) ); diff --git a/includes/specials/SpecialExpandTemplates.php b/includes/specials/SpecialExpandTemplates.php index 269dff6891..f3adeba335 100644 --- a/includes/specials/SpecialExpandTemplates.php +++ b/includes/specials/SpecialExpandTemplates.php @@ -151,7 +151,7 @@ class SpecialExpandTemplates extends SpecialPage { 'contexttitle', 60, $title, - array( 'autofocus' => true ) + array( 'autofocus' => true, 'class' => 'mw-ui-input-inline' ) ) . '

'; $form .= '

' . Xml::label( $this->msg( 'expand_templates_input' )->text(), @@ -251,6 +251,7 @@ class SpecialExpandTemplates extends SpecialPage { ) ) ); $out->addParserOutputContent( $pout ); $out->addHTML( Html::closeElement( 'div' ) ); + $out->setCategoryLinks( $pout->getCategories() ); } protected function getGroupName() { diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index eab4784cef..3d762aa313 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -30,6 +30,7 @@ * @ingroup SpecialPage */ class SpecialImport extends SpecialPage { + private $sourceName = false; private $interwiki = false; private $subproject; private $fullInterwikiPrefix; @@ -98,7 +99,7 @@ class SpecialImport extends SpecialPage { $isUpload = false; $request = $this->getRequest(); $this->namespace = $request->getIntOrNull( 'namespace' ); - $sourceName = $request->getVal( "source" ); + $this->sourceName = $request->getVal( "source" ); $this->logcomment = $request->getText( 'log-comment' ); $this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0 @@ -109,14 +110,14 @@ class SpecialImport extends SpecialPage { $user = $this->getUser(); if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) { $source = Status::newFatal( 'import-token-mismatch' ); - } elseif ( $sourceName == 'upload' ) { + } elseif ( $this->sourceName == 'upload' ) { $isUpload = true; if ( $user->isAllowed( 'importupload' ) ) { $source = ImportStreamSource::newFromUpload( "xmlimport" ); } else { throw new PermissionsError( 'importupload' ); } - } elseif ( $sourceName == "interwiki" ) { + } elseif ( $this->sourceName == "interwiki" ) { if ( !$user->isAllowed( 'import' ) ) { throw new PermissionsError( 'import' ); } @@ -250,7 +251,8 @@ class SpecialImport extends SpecialPage { Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) . " " . - Xml::input( 'log-comment', 50, '', + Xml::input( 'log-comment', 50, + ( $this->sourceName == 'upload' ? $this->logcomment : '' ), array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' . " @@ -430,7 +432,8 @@ class SpecialImport extends SpecialPage { Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) . " " . - Xml::input( 'log-comment', 50, '', + Xml::input( 'log-comment', 50, + ( $this->sourceName == 'interwiki' ? $this->logcomment : '' ), array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' . " diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php index 371469bb27..37edc0f95c 100644 --- a/includes/specials/SpecialLinkSearch.php +++ b/includes/specials/SpecialLinkSearch.php @@ -88,7 +88,7 @@ class LinkSearchPage extends QueryPage { $request = $this->getRequest(); $target = $request->getVal( 'target', $par ); - $namespace = $request->getIntorNull( 'namespace', null ); + $namespace = $request->getIntOrNull( 'namespace', null ); $protocols_list = array(); foreach ( $this->getConfig()->get( 'UrlProtocols' ) as $prot ) { diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php index 2a97abc89d..bf17a202a8 100644 --- a/includes/specials/SpecialListfiles.php +++ b/includes/specials/SpecialListfiles.php @@ -519,6 +519,7 @@ class ImageListPager extends TablePager { ); } + $this->getOutput()->addModules( 'mediawiki.userSuggest' ); $fields['user'] = array( 'type' => 'text', 'name' => 'user', @@ -527,6 +528,7 @@ class ImageListPager extends TablePager { 'default' => $this->mUserName, 'size' => '40', 'maxlength' => '255', + 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest ); $fields['ilshowall'] = array( diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index dc33801d5b..d3aa6c48a7 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -47,6 +47,7 @@ class SpecialLog extends SpecialPage { public function execute( $par ) { $this->setHeaders(); $this->outputHeader(); + $this->getOutput()->addModules( 'mediawiki.userSuggest' ); $opts = new FormOptions; $opts->add( 'type', '' ); diff --git a/includes/specials/SpecialMediaStatistics.php b/includes/specials/SpecialMediaStatistics.php index 3fd84138e1..1084482434 100644 --- a/includes/specials/SpecialMediaStatistics.php +++ b/includes/specials/SpecialMediaStatistics.php @@ -153,7 +153,8 @@ class MediaStatisticsPage extends QueryPage { ); $row .= Html::rawElement( 'td', - array(), + // Make sure js sorts it in numeric order + array( 'data-sort-value' => $count ), $this->msg( 'mediastatistics-nfiles' ) ->numParams( $count ) /** @todo Check to be sure this really should have number formatting */ diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index 546c191405..b9d18729c3 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -201,7 +201,10 @@ class NewFilesPager extends ReverseChronologicalPager { $context = new DerivativeContext( $this->getContext() ); $context->setTitle( $this->getTitle() ); // Remove subpage $form = new HTMLForm( $fields, $context ); + $form->setSubmitTextMsg( 'ilsubmit' ); + $form->setSubmitProgressive(); + $form->setMethod( 'get' ); $form->setWrapperLegendMsg( 'newimages-legend' ); diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index adc248eafa..c922fbda01 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -197,14 +197,6 @@ class SpecialSearch extends SpecialPage { $title = Title::newFromText( $term ); if ( !is_null( $title ) ) { wfRunHooks( 'SpecialSearchNogomatch', array( &$title ) ); - wfDebugLog( 'nogomatch', $title->getFullText(), 'private' ); - - # If the feature is enabled, go straight to the edit page - if ( $this->getConfig()->get( 'GoToEdit' ) ) { - $this->getOutput()->redirect( $title->getFullURL( array( 'action' => 'edit' ) ) ); - - return; - } } $this->showResults( $term ); } @@ -591,7 +583,7 @@ class SpecialSearch extends SpecialPage { $title = $result->getTitle(); - $titleSnippet = $result->getTitleSnippet( $terms ); + $titleSnippet = $result->getTitleSnippet(); if ( $titleSnippet == '' ) { $titleSnippet = null; @@ -623,9 +615,9 @@ class SpecialSearch extends SpecialPage { // format redirects / relevant sections $redirectTitle = $result->getRedirectTitle(); - $redirectText = $result->getRedirectSnippet( $terms ); + $redirectText = $result->getRedirectSnippet(); $sectionTitle = $result->getSectionTitle(); - $sectionText = $result->getSectionSnippet( $terms ); + $sectionText = $result->getSectionSnippet(); $redirect = ''; if ( !is_null( $redirectTitle ) ) { @@ -1068,9 +1060,9 @@ class SpecialSearch extends SpecialPage { 'class' => 'mw-ui-input mw-ui-input-inline', ) ) . "\n"; $out .= Html::hidden( 'fulltext', 'Search' ) . "\n"; - $out .= Xml::submitButton( + $out .= Html::submitButton( $this->msg( 'searchbutton' )->text(), - array( 'class' => array( 'mw-ui-button', 'mw-ui-progressive' ) ) + array(), array( 'mw-ui-progressive' ) ) . "\n"; // Results-info diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 6de7c90d11..bdd6751ea8 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -105,9 +105,12 @@ class LoginForm extends SpecialPage { * @param WebRequest $request */ public function __construct( $request = null ) { + global $wgUseMediaWikiUIEverywhere; parent::__construct( 'Userlogin' ); $this->mOverrideRequest = $request; + // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui + $wgUseMediaWikiUIEverywhere = true; } /** @@ -1423,16 +1426,9 @@ class LoginForm extends SpecialPage { } $template->set( 'secureLoginUrl', $this->mSecureLoginUrl ); - // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise - // Ditto for signupend. New forms use neither. + // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise $usingHTTPS = $this->mRequest->getProtocol() == 'https'; - $loginendHTTPS = $this->msg( 'loginend-https' ); $signupendHTTPS = $this->msg( 'signupend-https' ); - if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) { - $template->set( 'loginend', $loginendHTTPS->parse() ); - } else { - $template->set( 'loginend', $this->msg( 'loginend' )->parse() ); - } if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) { $template->set( 'signupend', $signupendHTTPS->parse() ); } else { diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 3508823cb3..6b9173f6fe 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -525,8 +525,17 @@ class SpecialVersion extends SpecialPage { ); array_walk( $tags, function ( &$value ) { - $value = '<' . htmlspecialchars( $value ) . '>'; + // Bidirectional isolation improves readability in RTL wikis + $value = Html::element( + 'bdi', + // Prevent < and > from slipping to another line + array( + 'style' => 'white-space: nowrap;', + ), + "<$value>" + ); } ); + $out .= $this->listToText( $tags ); } else { $out = ''; diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php index 937a503c0f..16127d9e9d 100644 --- a/includes/specials/SpecialWantedfiles.php +++ b/includes/specials/SpecialWantedfiles.php @@ -102,7 +102,7 @@ class WantedFilesPage extends WantedQueryPage { * @return boolean */ protected function existenceCheck( Title $title ) { - return (bool) wfFindFile( $title ); + return (bool)wfFindFile( $title ); } function getQueryInfo() { diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 8f2f86b92a..7becfaae11 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -562,12 +562,10 @@ class SpecialWatchlist extends ChangesListSpecialPage { protected function daysLink( $d, $options = array() ) { $options['days'] = $d; - $message = $d ? $this->getLanguage()->formatNum( $d ) - : $this->msg( 'watchlistall2' )->escaped(); return Linker::linkKnown( $this->getPageTitle(), - $message, + $this->getLanguage()->formatNum( $d ), array(), $options ); @@ -581,8 +579,11 @@ class SpecialWatchlist extends ChangesListSpecialPage { * @return string */ protected function cutoffLinks( $days, $options = array() ) { + global $wgRCMaxAge; + $watchlistMaxDays = ceil( $wgRCMaxAge / ( 3600 * 24 ) ); + $hours = array( 1, 2, 6, 12 ); - $days = array( 1, 3, 7 ); + $days = array( 1, 3, 7, $watchlistMaxDays ); $i = 0; foreach ( $hours as $h ) { $hours[$i++] = $this->hoursLink( $h, $options ); @@ -594,8 +595,7 @@ class SpecialWatchlist extends ChangesListSpecialPage { return $this->msg( 'wlshowlast' )->rawParams( $this->getLanguage()->pipeList( $hours ), - $this->getLanguage()->pipeList( $days ), - $this->daysLink( 0, $options ) )->parse(); + $this->getLanguage()->pipeList( $days ) )->parse(); } /** diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php index 7dc6da1f70..e373cfffb6 100644 --- a/includes/specials/SpecialWhatlinkshere.php +++ b/includes/specials/SpecialWhatlinkshere.php @@ -431,9 +431,9 @@ class SpecialWhatLinksHere extends IncludableSpecialPage { $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() ); - # Target input + # Target input (.mw-searchInput enables suggestions) $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target', - 'mw-whatlinkshere-target', 40, $target ); + 'mw-whatlinkshere-target', 40, $target, array( 'class' => 'mw-searchInput' ) ); $f .= ' '; diff --git a/includes/templates/Usercreate.php b/includes/templates/Usercreate.php index 01da0bd790..4eea01c3d0 100644 --- a/includes/templates/Usercreate.php +++ b/includes/templates/Usercreate.php @@ -254,14 +254,17 @@ class UsercreateTemplate extends BaseTemplate { ?>

getMsg( $this->data['loggedin'] ? 'createacct-another-submit' : 'createacct-submit' ), - 'submit', - array( - 'class' => "mw-ui-button mw-ui-big mw-ui-block mw-ui-constructive", + $attrs = array( 'id' => 'wpCreateaccount', + 'name' => 'wpCreateaccount', 'tabindex' => $tabIndex++ + ), + array( + 'mw-ui-big', + 'mw-ui-block', + 'mw-ui-constructive', ) ); ?> diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php index 8bba4265f0..2a9badf224 100644 --- a/includes/templates/Userlogin.php +++ b/includes/templates/Userlogin.php @@ -148,11 +148,15 @@ class UserloginTemplate extends BaseTemplate {
getMsg( 'pt-login-button' )->text(), 'submit', array( + $attrs = array( 'id' => 'wpLoginAttempt', + 'name' => 'wpLoginAttempt', 'tabindex' => '6', - 'class' => 'mw-ui-button mw-ui-big mw-ui-block mw-ui-constructive' - ) ); + ); + $modifiers = array( + 'mw-ui-big', 'mw-ui-block', 'mw-ui-constructive', + ); + echo Html::submitButton( $this->getMsg( 'pt-login-button' )->text(), $attrs, $modifiers ); ?>
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index 5de543e0ee..079c7f8eb3 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -69,8 +69,6 @@ abstract class UploadBase { const WINDOWS_NONASCII_FILENAME = 13; const FILENAME_TOO_LONG = 14; - const SESSION_STATUS_KEY = 'wsUploadStatusData'; - /** * @param int $error * @return string @@ -746,6 +744,8 @@ abstract class UploadBase { ); } wfRunHooks( 'UploadComplete', array( &$this ) ); + + $this->postProcessUpload(); } wfProfileOut( __METHOD__ ); @@ -753,6 +753,35 @@ abstract class UploadBase { return $status; } + /** + * Perform extra steps after a successful upload. + * + * @since 1.25 + */ + public function postProcessUpload() { + global $wgUploadThumbnailRenderMap; + + $jobs = array(); + + $sizes = $wgUploadThumbnailRenderMap; + rsort( $sizes ); + + $file = $this->getLocalFile(); + + foreach ( $sizes as $size ) { + if ( $file->isVectorized() + || $file->getWidth() > $size ) { + $jobs[] = new ThumbnailRenderJob( $file->getTitle(), array( + 'transformParams' => array( 'width' => $size ), + ) ); + } + } + + if ( $jobs ) { + JobQueueGroup::singleton()->push( $jobs ); + } + } + /** * Returns the title of the file to be uploaded. Sets mTitleError in case * the name was illegal. @@ -1297,7 +1326,8 @@ abstract class UploadBase { * @param array $attribs * @return bool */ - public function checkSvgScriptCallback( $element, $attribs ) { + public function checkSvgScriptCallback( $element, $attribs, $data = null ) { + list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element ); // We specifically don't include: @@ -1381,6 +1411,14 @@ abstract class UploadBase { return true; } + # Check + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/add-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/add-invert.svg new file mode 100644 index 0000000000..93a1c6efa8 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/add-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/add.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/add.svg new file mode 100644 index 0000000000..29e5dba8c6 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/add.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-invert.svg new file mode 100644 index 0000000000..c3e43d13fc --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced.svg new file mode 100644 index 0000000000..201b4d7347 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-invert.svg new file mode 100644 index 0000000000..715a3f552f --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-invert.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert.svg new file mode 100644 index 0000000000..f0c652245c --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-invert.svg new file mode 100644 index 0000000000..1874597fc2 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr.svg new file mode 100644 index 0000000000..8a670ef209 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-invert.svg new file mode 100644 index 0000000000..75b23b44d0 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl.svg new file mode 100644 index 0000000000..01fc216be5 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-invert.svg new file mode 100644 index 0000000000..3e42ba80ac --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-primary.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-primary.svg new file mode 100644 index 0000000000..6139b582d8 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-primary.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check.svg new file mode 100644 index 0000000000..8d4a1f8b8c --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-invert.svg new file mode 100644 index 0000000000..6d3bc588f8 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear.svg new file mode 100644 index 0000000000..0dcde9d182 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-invert.svg new file mode 100644 index 0000000000..0305027196 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/close.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close.svg new file mode 100644 index 0000000000..1345e86771 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-invert.svg new file mode 100644 index 0000000000..bc4ae94311 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-invert.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/code.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/code.svg new file mode 100644 index 0000000000..32f140d987 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/code.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-invert.svg new file mode 100644 index 0000000000..451e0fee27 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse.svg new file mode 100644 index 0000000000..55aa8f8f9a --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-invert.svg new file mode 100644 index 0000000000..b6152fe9d5 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment.svg new file mode 100644 index 0000000000..0ae7e63f6c --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-invert.svg new file mode 100644 index 0000000000..a3cadb489a --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand.svg new file mode 100644 index 0000000000..7666b41d60 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-invert.svg new file mode 100644 index 0000000000..3670661a0e --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-invert.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/help.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help.svg new file mode 100644 index 0000000000..bb2545c58b --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.svg new file mode 100644 index 0000000000..be4d2c7555 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/info.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info.svg new file mode 100644 index 0000000000..9c0d1cbc26 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-invert.svg new file mode 100644 index 0000000000..01ce113c12 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-invert.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/link.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link.svg new file mode 100644 index 0000000000..dbae3414bc --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-invert.svg new file mode 100644 index 0000000000..dddbbb8d51 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu.svg new file mode 100644 index 0000000000..50ac8a3981 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-invert.svg new file mode 100644 index 0000000000..10f0c4e588 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr.svg new file mode 100644 index 0000000000..51e6611afb --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-invert.svg new file mode 100644 index 0000000000..002ec0f67b --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl.svg new file mode 100644 index 0000000000..bcee09d96e --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/picture-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/picture-invert.svg new file mode 100644 index 0000000000..55e0b7f148 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/picture-invert.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/picture.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/picture.svg new file mode 100644 index 0000000000..7400bca974 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/picture.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove-destructive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove-destructive.svg new file mode 100644 index 0000000000..be51bea0c1 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove-destructive.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove-invert.svg new file mode 100644 index 0000000000..ddc352fe16 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove.svg new file mode 100644 index 0000000000..6ad791744e --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/remove.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-invert.svg new file mode 100644 index 0000000000..5ae49526ec --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/search.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search.svg new file mode 100644 index 0000000000..e4db4f0d07 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-invert.svg new file mode 100644 index 0000000000..d7ad1e50c1 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings.svg new file mode 100644 index 0000000000..9fa0a4b348 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-invert.svg new file mode 100644 index 0000000000..e57342956c --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag.svg new file mode 100644 index 0000000000..534824c843 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-invert.svg new file mode 100644 index 0000000000..0aeb616895 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-invert.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/window.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/window.svg new file mode 100644 index 0000000000..cd3b76c207 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/window.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-invert.svg new file mode 100644 index 0000000000..cd666495db --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert.svg new file mode 100644 index 0000000000..d9dc6a87e5 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-invert.svg new file mode 100644 index 0000000000..31a561a98a --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down.svg new file mode 100644 index 0000000000..d64695fb66 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-invert.svg new file mode 100644 index 0000000000..5816c08b31 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr.svg new file mode 100644 index 0000000000..7bccea12cf --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-invert.svg new file mode 100644 index 0000000000..01e40d7a70 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl.svg new file mode 100644 index 0000000000..304c516826 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-invert.svg new file mode 100644 index 0000000000..e880711059 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up.svg new file mode 100644 index 0000000000..4769526e65 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-invert.svg new file mode 100644 index 0000000000..30baa50cb3 --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required.svg new file mode 100644 index 0000000000..969fa2d84a --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/textures/pending.gif b/resources/lib/oojs-ui/themes/mediawiki/images/textures/pending.gif new file mode 100644 index 0000000000..1194eed293 Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/textures/pending.gif differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/textures/transparency.svg b/resources/lib/oojs-ui/themes/mediawiki/images/textures/transparency.svg new file mode 100644 index 0000000000..63a0b57cfd --- /dev/null +++ b/resources/lib/oojs-ui/themes/mediawiki/images/textures/transparency.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/lib/oojs-ui/themes/minerva/images/icons/check-invert.svg b/resources/lib/oojs-ui/themes/minerva/images/icons/check-invert.svg new file mode 100644 index 0000000000..12d7f0d317 --- /dev/null +++ b/resources/lib/oojs-ui/themes/minerva/images/icons/check-invert.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lib/oojs-ui/themes/minerva/images/icons/check.png b/resources/lib/oojs-ui/themes/minerva/images/icons/check.png deleted file mode 100644 index 65026a0fd3..0000000000 Binary files a/resources/lib/oojs-ui/themes/minerva/images/icons/check.png and /dev/null differ diff --git a/resources/lib/oojs-ui/themes/minerva/images/icons/check.svg b/resources/lib/oojs-ui/themes/minerva/images/icons/check.svg index 6a91939f3a..248c6362d6 100644 --- a/resources/lib/oojs-ui/themes/minerva/images/icons/check.svg +++ b/resources/lib/oojs-ui/themes/minerva/images/icons/check.svg @@ -1,8 +1,6 @@ - - - - - - + + + + + + diff --git a/resources/src/jquery.json-deprecate.js b/resources/src/jquery.json-deprecate.js deleted file mode 100644 index f38decd9a0..0000000000 --- a/resources/src/jquery.json-deprecate.js +++ /dev/null @@ -1,8 +0,0 @@ -( function ( mw, $ ) { - // @deprecated since 1.24. The 'jquery.json' module will be removed in MW 1.25. Use the 'json' module. - - mw.log.deprecate( $, 'toJSON', $.toJSON, 'Use JSON.stringify instead (module "json" for polyfill).' ); - mw.log.deprecate( $, 'evalJSON', $.evalJSON, 'Use JSON.parse instead (module "json" for polyfill).' ); - mw.log.deprecate( $, 'secureEvalJSON', $.secureEvalJSON, 'Use JSON.parse instead (module "json" for polyfill).' ); - mw.log.deprecate( $, 'quoteString', $.quoteString, 'Use JSON.stringify instead (module "json" for polyfill).' ); -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.editWarning.js b/resources/src/mediawiki.action/mediawiki.action.edit.editWarning.js index b5654400f7..6b33012854 100644 --- a/resources/src/mediawiki.action/mediawiki.action.edit.editWarning.js +++ b/resources/src/mediawiki.action/mediawiki.action.edit.editWarning.js @@ -5,54 +5,37 @@ 'use strict'; $( function () { - var savedWindowOnBeforeUnload, - $wpTextbox1 = $( '#wpTextbox1' ), - $wpSummary = $( '#wpSummary' ); + var allowCloseWindow, + $textBox = $( '#wpTextbox1' ), + $summary = $( '#wpSummary' ), + $both = $textBox.add( $summary ); + // Check if EditWarning is enabled and if we need it - if ( $wpTextbox1.length === 0 ) { + if ( !mw.user.options.get( 'useeditwarning' ) ) { return true; } - // Get the original values of some form elements - $wpTextbox1.add( $wpSummary ).each( function () { - $( this ).data( 'origtext', $( this ).val() ); + + // Save the original value of the text fields + $both.each( function ( index, element ) { + var $element = $( element ); + $element.data( 'origtext', $element.textSelection( 'getContents' ) ); } ); - $( window ) - .on( 'beforeunload.editwarning', function () { - var retval; - // Check if the current values of some form elements are the same as - // the original values - if ( - mw.config.get( 'wgAction' ) === 'submit' || - $wpTextbox1.data( 'origtext' ) !== $wpTextbox1.textSelection( 'getContents' ) || - $wpSummary.data( 'origtext' ) !== $wpSummary.textSelection( 'getContents' ) - ) { - // Return our message - retval = mw.msg( 'editwarning-warning' ); - } + allowCloseWindow = mw.confirmCloseWindow( { + test: function () { + // We use .textSelection, because editors might not have updated the form yet. + return mw.config.get( 'wgAction' ) === 'submit' || + $textBox.data( 'origtext' ) !== $textBox.textSelection( 'getContents' ) || + $summary.data( 'origtext' ) !== $summary.textSelection( 'getContents' ); + }, - // Unset the onbeforeunload handler so we don't break page caching in Firefox - savedWindowOnBeforeUnload = window.onbeforeunload; - window.onbeforeunload = null; - if ( retval !== undefined ) { - // ...but if the user chooses not to leave the page, we need to rebind it - setTimeout( function () { - window.onbeforeunload = savedWindowOnBeforeUnload; - }, 1 ); - return retval; - } - } ) - .on( 'pageshow.editwarning', function () { - // Re-add onbeforeunload handler - if ( !window.onbeforeunload ) { - window.onbeforeunload = savedWindowOnBeforeUnload; - } - } ); + message: mw.msg( 'editwarning-warning' ), + namespace: 'editwarning' + } ); // Add form submission handler $( '#editform' ).submit( function () { - // Unbind our handlers - $( window ).off( '.editwarning' ); + allowCloseWindow(); } ); } ); diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.js b/resources/src/mediawiki.action/mediawiki.action.edit.js index 4519b04927..f88b83667d 100644 --- a/resources/src/mediawiki.action/mediawiki.action.edit.js +++ b/resources/src/mediawiki.action/mediawiki.action.edit.js @@ -1,195 +1,10 @@ -/** - * Interface for the classic edit toolbar. - * - * @class mw.toolbar - * @singleton +/*! + * Scripts for action=edit */ ( function ( mw, $ ) { - var toolbar, isReady, $toolbar, queue, slice, $currentFocused; - - /** - * Internal helper that does the actual insertion of the button into the toolbar. - * - * See #addButton for parameter documentation. - * - * @private - */ - function insertButton( b, speedTip, tagOpen, tagClose, sampleText, imageId ) { - var $button; - - // Backwards compatibility - if ( typeof b !== 'object' ) { - b = { - imageFile: b, - speedTip: speedTip, - tagOpen: tagOpen, - tagClose: tagClose, - sampleText: sampleText, - imageId: imageId - }; - } - - if ( b.imageFile ) { - $button = $( '' ).attr( { - src: b.imageFile, - alt: b.speedTip, - title: b.speedTip, - id: b.imageId || undefined, - 'class': 'mw-toolbar-editbutton' - } ); - } else { - $button = $( '
' ).attr( { - title: b.speedTip, - id: b.imageId || undefined, - 'class': 'mw-toolbar-editbutton' - } ); - } - - $button.click( function ( e ) { - if ( b.onClick !== undefined ) { - b.onClick( e ); - } else { - toolbar.insertTags( b.tagOpen, b.tagClose, b.sampleText ); - } - - return false; - } ); - - $toolbar.append( $button ); - } - - isReady = false; - $toolbar = false; - - /** - * @private - * @property {Array} - * Contains button objects (and for backwards compatibilty, it can - * also contains an arguments array for insertButton). - */ - queue = []; - slice = queue.slice; - - toolbar = { - - /** - * Add buttons to the toolbar. - * - * Takes care of race conditions and time-based dependencies - * by placing buttons in a queue if this method is called before - * the toolbar is created. - * - * For backwards-compatibility, passing `imageFile`, `speedTip`, `tagOpen`, `tagClose`, - * `sampleText` and `imageId` as separate arguments (in this order) is also supported. - * - * @param {Object} button Object with the following properties. - * You are required to provide *either* the `onClick` parameter, or the three parameters - * `tagOpen`, `tagClose` and `sampleText`, but not both (they're mutually exclusive). - * @param {string} [button.imageFile] Image to use for the button. - * @param {string} button.speedTip Tooltip displayed when user mouses over the button. - * @param {Function} [button.onClick] Function to be executed when the button is clicked. - * @param {string} [button.tagOpen] - * @param {string} [button.tagClose] - * @param {string} [button.sampleText] Alternative to `onClick`. `tagOpen`, `tagClose` and - * `sampleText` together provide the markup that should be inserted into page text at - * current cursor position. - * @param {string} [button.imageId] `id` attribute of the button HTML element. Can be - * used to define the image with CSS if it's not provided as `imageFile`. - */ - addButton: function () { - if ( isReady ) { - insertButton.apply( toolbar, arguments ); - } else { - // Convert arguments list to array - queue.push( slice.call( arguments ) ); - } - }, - /** - * Add multiple buttons to the toolbar (see also #addButton). - * - * Example usage: - * - * addButtons( [ { .. }, { .. }, { .. } ] ); - * addButtons( { .. }, { .. } ); - * - * @param {Object|Array...} [buttons] An array of button objects or the first - * button object in a list of variadic arguments. - */ - addButtons: function ( buttons ) { - if ( !$.isArray( buttons ) ) { - buttons = slice.call( arguments ); - } - if ( isReady ) { - $.each( buttons, function () { - insertButton( this ); - } ); - } else { - // Push each button into the queue - queue.push.apply( queue, buttons ); - } - }, - - /** - * Apply tagOpen/tagClose to selection in currently focused textarea. - * - * Uses `sampleText` if selection is empty. - * - * @param {string} tagOpen - * @param {string} tagClose - * @param {string} sampleText - */ - insertTags: function ( tagOpen, tagClose, sampleText ) { - if ( $currentFocused && $currentFocused.length ) { - $currentFocused.textSelection( - 'encapsulateSelection', { - pre: tagOpen, - peri: sampleText, - post: tagClose - } - ); - } - }, - - // For backwards compatibility, - // Called from EditPage.php, maybe in other places as well. - init: function () {} - }; - - // Legacy (for compatibility with the code previously in skins/common.edit.js) - mw.log.deprecate( window, 'addButton', toolbar.addButton, 'Use mw.toolbar.addButton instead.' ); - mw.log.deprecate( window, 'insertTags', toolbar.insertTags, 'Use mw.toolbar.insertTags instead.' ); - - // Expose API publicly - mw.toolbar = toolbar; $( function () { - var i, b, editBox, scrollTop, $editForm; - - // Used to determine where to insert tags - $currentFocused = $( '#wpTextbox1' ); - - // Populate the selector cache for $toolbar - $toolbar = $( '#toolbar' ); - - for ( i = 0; i < queue.length; i++ ) { - b = queue[i]; - if ( $.isArray( b ) ) { - // Forwarded arguments array from mw.toolbar.addButton - insertButton.apply( toolbar, b ); - } else { - // Raw object from mw.toolbar.addButtons - insertButton( b ); - } - } - - // Clear queue - queue.length = 0; - - // This causes further calls to addButton to go to insertion directly - // instead of to the queue. - // It is important that this is after the one and only loop through - // the the queue - isReady = true; + var editBox, scrollTop, $editForm; // Make sure edit summary does not exceed byte limit $( '#wpSummary' ).byteLimit( 255 ); @@ -207,11 +22,6 @@ scrollTop.value = editBox.scrollTop; } ); } - - // Apply to dynamically created textboxes as well as normal ones - $( document ).on( 'focus', 'textarea, input:text', function () { - $currentFocused = $( this ); - } ); } ); }( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.styles.css b/resources/src/mediawiki.action/mediawiki.action.edit.styles.css index 7148b9649a..4209aa1f14 100644 --- a/resources/src/mediawiki.action/mediawiki.action.edit.styles.css +++ b/resources/src/mediawiki.action/mediawiki.action.edit.styles.css @@ -8,14 +8,6 @@ display: block; } -.editOptions { - background-color: #F0F0F0; - border: 1px solid silver; - border-top: none; - padding: 1em 1em 1.5em 1em; - margin-bottom: 2em; -} - /* Adjustments to edit form elements */ .editCheckboxes { margin-bottom: 1em; diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_bold.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_bold.png deleted file mode 100644 index e524f6cbad..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_bold.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_headline.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_headline.png deleted file mode 100644 index 398e5614fe..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_headline.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_italic.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_italic.png deleted file mode 100644 index 6ec73e9e32..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_italic.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_link.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_link.png deleted file mode 100644 index c9c63f6c56..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_link.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_nowiki.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_nowiki.png deleted file mode 100644 index 743ea61b1e..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ar/button_nowiki.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_bold.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_bold.png deleted file mode 100644 index 5c10cfe205..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_bold.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_italic.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_italic.png deleted file mode 100644 index 72209d74ba..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_italic.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_link.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_link.png deleted file mode 100644 index 09c86fb14a..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/be-tarask/button_link.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/de/button_bold.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/de/button_bold.png deleted file mode 100644 index 367d5bc149..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/de/button_bold.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/de/button_italic.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/de/button_italic.png deleted file mode 100644 index fdd8c9f924..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/de/button_italic.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_bold.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_bold.png deleted file mode 100644 index 75c3f1094a..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_bold.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_extlink.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_extlink.png deleted file mode 100644 index 458943c1d1..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_extlink.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_headline.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_headline.png deleted file mode 100644 index 9cf751d9d2..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_headline.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_hr.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_hr.png deleted file mode 100644 index 47e1ca4013..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_hr.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_image.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_image.png deleted file mode 100644 index 69192965c4..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_image.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_italic.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_italic.png deleted file mode 100644 index 527fbd1455..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_italic.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_link.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_link.png deleted file mode 100644 index eb5634b910..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_link.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_media.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_media.png deleted file mode 100644 index 4194ec1875..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_media.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_nowiki.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_nowiki.png deleted file mode 100644 index 2ba818de2b..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_nowiki.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_sig.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_sig.png deleted file mode 100644 index fe34b3fb9a..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/en/button_sig.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_bold.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_bold.png deleted file mode 100644 index c54d094cad..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_bold.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_headline.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_headline.png deleted file mode 100644 index 9890d15504..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_headline.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_italic.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_italic.png deleted file mode 100644 index 33f91ed613..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_italic.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_link.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_link.png deleted file mode 100644 index 76b939e6af..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_link.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_nowiki.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_nowiki.png deleted file mode 100644 index 743ea61b1e..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/fa/button_nowiki.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ksh/LICENSE b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ksh/LICENSE deleted file mode 100644 index 47ecfe4ed2..0000000000 --- a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ksh/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ - -button_italic.png -------------------- -Source : http://commons.wikimedia.org/wiki/Image:Button_S_italic.png -License: Public domain -Author : Purodha Blissenbach, http://ksh.wikipedia.org/wiki/User:Purodha - diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ksh/button_italic.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ksh/button_italic.png deleted file mode 100644 index 15496c082c..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ksh/button_italic.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/LICENSE b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/LICENSE deleted file mode 100644 index bedcec6621..0000000000 --- a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ -button_bold.png ---------------- -Source : http://commons.wikimedia.org/wiki/Image:Button_bold_ukr.png -License: Public domain -Author : Alexey Belomoev - -button_italic.png ------------------------- -Source : http://commons.wikimedia.org/wiki/Image:Button_italic_ukr.png -License: Public domain -Author : Alexey Belomoev - -button_link.png ------------------ -Source : http://commons.wikimedia.org/wiki/Image:Button_internal_link_ukr.png -License: GPL -Author : Saproj, Erik Möller diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_bold.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_bold.png deleted file mode 100644 index eae30d98e5..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_bold.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_italic.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_italic.png deleted file mode 100644 index b958d2205c..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_italic.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_link.png b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_link.png deleted file mode 100644 index 12ad373192..0000000000 Binary files a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/images/ru/button_link.png and /dev/null differ diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/mediawiki.action.edit.toolbar.less b/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/mediawiki.action.edit.toolbar.less deleted file mode 100644 index d65b284217..0000000000 --- a/resources/src/mediawiki.action/mediawiki.action.edit.toolbar/mediawiki.action.edit.toolbar.less +++ /dev/null @@ -1,42 +0,0 @@ -@import "mediawiki.mixins"; - -#mw-editbutton-bold { - .background-image("images/@{button-bold}"); -} - -#mw-editbutton-italic { - .background-image("images/@{button-italic}"); -} - -#mw-editbutton-link { - .background-image("images/@{button-link}"); -} - -#mw-editbutton-extlink { - .background-image("images/@{button-extlink}"); -} - -#mw-editbutton-headline { - .background-image("images/@{button-headline}"); -} - -#mw-editbutton-image { - .background-image("images/@{button-image}"); -} - -#mw-editbutton-media { - .background-image("images/@{button-media}"); -} - -#mw-editbutton-nowiki { - .background-image("images/@{button-nowiki}"); -} - -// Who decided to make only this single one different than the name of the data item? -#mw-editbutton-signature { - .background-image("images/@{button-sig}"); -} - -#mw-editbutton-hr { - .background-image("images/@{button-hr}"); -} diff --git a/resources/src/mediawiki.action/mediawiki.action.history.diff.css b/resources/src/mediawiki.action/mediawiki.action.history.diff.css index afe9246842..092a59768e 100644 --- a/resources/src/mediawiki.action/mediawiki.action.history.diff.css +++ b/resources/src/mediawiki.action/mediawiki.action.history.diff.css @@ -2,7 +2,6 @@ ** Diff rendering */ table.diff { - background-color: white; border: none; border-spacing: 4px; margin: 0; diff --git a/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js b/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js index 2ded40cf2f..3c22851e37 100644 --- a/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js +++ b/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js @@ -4,9 +4,12 @@ ( function ( mw, $ ) { $( function () { mw.util.$content.dblclick( function ( e ) { - e.preventDefault(); - // Trigger native HTMLElement click instead of opening URL (bug 43052) - $( '#ca-edit a' ).get( 0 ).click(); + // Recheck preference so extensions can do a hack to disable this code. + if ( parseInt( mw.user.options.get( 'editondblclick' ), 10 ) ) { + e.preventDefault(); + // Trigger native HTMLElement click instead of opening URL (bug 43052) + $( '#ca-edit a' ).get( 0 ).click(); + } } ); } ); }( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.action/mediawiki.action.view.redirect.js b/resources/src/mediawiki.action/mediawiki.action.view.redirect.js index b9aa137e31..52e0d4e3d2 100644 --- a/resources/src/mediawiki.action/mediawiki.action.view.redirect.js +++ b/resources/src/mediawiki.action/mediawiki.action.view.redirect.js @@ -14,11 +14,6 @@ // Clear internal mw.config entries, so that no one tries to depend on them mw.config.set( 'wgInternalRedirectTargetUrl', null ); - // Deployment hack for compatibility with cached HTML, remove before 1.24 release - if ( !canonical ) { - canonical = mw.config.get( 'wgRedirectToFragment' ); - } - index = canonical.indexOf( '#' ); if ( index !== -1 ) { fragment = canonical.slice( index ); diff --git a/resources/src/mediawiki.api/mediawiki.api.category.js b/resources/src/mediawiki.api/mediawiki.api.category.js index 7dd9730f77..14077e022f 100644 --- a/resources/src/mediawiki.api/mediawiki.api.category.js +++ b/resources/src/mediawiki.api/mediawiki.api.category.js @@ -3,29 +3,21 @@ */ ( function ( mw, $ ) { - var msg = 'Use of mediawiki.api callback params is deprecated. Use the Promise instead.'; $.extend( mw.Api.prototype, { /** * Determine if a category exists. * * @param {mw.Title|string} title - * @param {Function} [ok] Success callback (deprecated) - * @param {Function} [err] Error callback (deprecated) * @return {jQuery.Promise} * @return {Function} return.done * @return {boolean} return.done.isCategory Whether the category exists. */ - isCategory: function ( title, ok, err ) { + isCategory: function ( title ) { var apiPromise = this.get( { prop: 'categoryinfo', titles: String( title ) } ); - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - } - return apiPromise .then( function ( data ) { var exists = false; @@ -38,8 +30,6 @@ } return exists; } ) - .done( ok ) - .fail( err ) .promise( { abort: apiPromise.abort } ); }, @@ -49,13 +39,11 @@ * E.g. given "Foo", return "Food", "Foolish people", "Foosball tables"... * * @param {string} prefix Prefix to match. - * @param {Function} [ok] Success callback (deprecated) - * @param {Function} [err] Error callback (deprecated) * @return {jQuery.Promise} * @return {Function} return.done * @return {string[]} return.done.categories Matched categories */ - getCategoriesByPrefix: function ( prefix, ok, err ) { + getCategoriesByPrefix: function ( prefix ) { // Fetch with allpages to only get categories that have a corresponding description page. var apiPromise = this.get( { list: 'allpages', @@ -63,11 +51,6 @@ apnamespace: mw.config.get( 'wgNamespaceIds' ).category } ); - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - } - return apiPromise .then( function ( data ) { var texts = []; @@ -78,8 +61,6 @@ } return texts; } ) - .done( ok ) - .fail( err ) .promise( { abort: apiPromise.abort } ); }, @@ -87,34 +68,17 @@ * Get the categories that a particular page on the wiki belongs to. * * @param {mw.Title|string} title - * @param {Function} [ok] Success callback (deprecated) - * @param {Function} [err] Error callback (deprecated) - * @param {boolean} [async=true] Asynchronousness (deprecated) * @return {jQuery.Promise} * @return {Function} return.done * @return {boolean|mw.Title[]} return.done.categories List of category titles or false * if title was not found. */ - getCategories: function ( title, ok, err, async ) { + getCategories: function ( title ) { var apiPromise = this.get( { prop: 'categories', titles: String( title ) - }, { - async: async === undefined ? true : async } ); - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - } - if ( async !== undefined ) { - mw.track( 'mw.deprecate', 'api.async' ); - mw.log.warn( - 'Use of mediawiki.api async=false param is deprecated. ' + - 'The sychronous mode will be removed in the future.' - ); - } - return apiPromise .then( function ( data ) { var titles = false; @@ -132,8 +96,6 @@ } return titles; } ) - .done( ok ) - .fail( err ) .promise( { abort: apiPromise.abort } ); } } ); diff --git a/resources/src/mediawiki.api/mediawiki.api.edit.js b/resources/src/mediawiki.api/mediawiki.api.edit.js index 714c38cd27..dbe45bf618 100644 --- a/resources/src/mediawiki.api/mediawiki.api.edit.js +++ b/resources/src/mediawiki.api/mediawiki.api.edit.js @@ -3,7 +3,6 @@ */ ( function ( mw, $ ) { - var msg = 'Use of mediawiki.api callback params is deprecated. Use the Promise instead.'; $.extend( mw.Api.prototype, { /** @@ -12,35 +11,21 @@ * cached token and start over. * * @param {Object} params API parameters - * @param {Function} [ok] Success callback (deprecated) - * @param {Function} [err] Error callback (deprecated) * @return {jQuery.Promise} See #post */ - postWithEditToken: function ( params, ok, err ) { - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - } - - return this.postWithToken( 'edit', params ).done( ok ).fail( err ); + postWithEditToken: function ( params ) { + return this.postWithToken( 'edit', params ); }, /** * API helper to grab an edit token. * - * @param {Function} [ok] Success callback (deprecated) - * @param {Function} [err] Error callback (deprecated) * @return {jQuery.Promise} * @return {Function} return.done * @return {string} return.done.token Received token. */ - getEditToken: function ( ok, err ) { - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - } - - return this.getToken( 'edit' ).done( ok ).fail( err ); + getEditToken: function () { + return this.getToken( 'edit' ); }, /** @@ -50,31 +35,16 @@ * @param {string} header * @param {string} message wikitext message * @param {Object} [additionalParams] Additional API parameters, e.g. `{ redirect: true }` - * @param {Function} [ok] Success handler (deprecated) - * @param {Function} [err] Error handler (deprecated) * @return {jQuery.Promise} */ - newSection: function ( title, header, message, additionalParams, ok, err ) { - // Until we remove 'ok' and 'err' parameters, we have to support code that passes them, - // but not additionalParams... - if ( $.isFunction( additionalParams ) ) { - err = ok; - ok = additionalParams; - additionalParams = undefined; - } - - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - } - + newSection: function ( title, header, message, additionalParams ) { return this.postWithEditToken( $.extend( { action: 'edit', section: 'new', title: String( title ), summary: header, text: message - }, additionalParams ) ).done( ok ).fail( err ); + }, additionalParams ) ); } } ); diff --git a/resources/src/mediawiki.api/mediawiki.api.js b/resources/src/mediawiki.api/mediawiki.api.js index e5c85dfa97..bb0642e0ea 100644 --- a/resources/src/mediawiki.api/mediawiki.api.js +++ b/resources/src/mediawiki.api/mediawiki.api.js @@ -74,32 +74,15 @@ mw.Api.prototype = { - /** - * Normalize the ajax options for compatibility and/or convenience methods. - * - * @param {Object} [arg] An object contaning one or more of options.ajax. - * @return {Object} Normalized ajax options. - */ - normalizeAjaxOptions: function ( arg ) { - // Arg argument is usually empty - // (before MW 1.20 it was used to pass ok callbacks) - var opts = arg || {}; - // Options can also be a success callback handler - if ( typeof arg === 'function' ) { - opts = { ok: arg }; - } - return opts; - }, - /** * Perform API get request * * @param {Object} parameters - * @param {Object|Function} [ajaxOptions] + * @param {Object} [ajaxOptions] * @return {jQuery.Promise} */ get: function ( parameters, ajaxOptions ) { - ajaxOptions = this.normalizeAjaxOptions( ajaxOptions ); + ajaxOptions = ajaxOptions || {}; ajaxOptions.type = 'GET'; return this.ajax( parameters, ajaxOptions ); }, @@ -110,11 +93,11 @@ * TODO: Post actions for non-local hostnames will need proxy. * * @param {Object} parameters - * @param {Object|Function} [ajaxOptions] + * @param {Object} [ajaxOptions] * @return {jQuery.Promise} */ post: function ( parameters, ajaxOptions ) { - ajaxOptions = this.normalizeAjaxOptions( ajaxOptions ); + ajaxOptions = ajaxOptions || {}; ajaxOptions.type = 'POST'; return this.ajax( parameters, ajaxOptions ); }, @@ -130,7 +113,6 @@ ajax: function ( parameters, ajaxOptions ) { var token, apiDeferred = $.Deferred(), - msg = 'Use of mediawiki.api callback params is deprecated. Use the Promise instead.', xhr, key, formData; parameters = $.extend( {}, this.defaults.parameters, parameters ); @@ -183,21 +165,6 @@ } } - // Backwards compatibility: Before MediaWiki 1.20, - // callbacks were done with the 'ok' and 'err' property in ajaxOptions. - if ( ajaxOptions.ok ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - apiDeferred.done( ajaxOptions.ok ); - delete ajaxOptions.ok; - } - if ( ajaxOptions.err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( msg ); - apiDeferred.fail( ajaxOptions.err ); - delete ajaxOptions.err; - } - // Make the AJAX request xhr = $.ajax( ajaxOptions ) // If AJAX fails, reject API call with error code 'http' @@ -251,12 +218,6 @@ postWithToken: function ( tokenType, params, ajaxOptions ) { var api = this; - // Do not allow deprecated ok-callback - // FIXME: Remove this check when the deprecated ok-callback is removed in #post - if ( $.isFunction( ajaxOptions ) ) { - ajaxOptions = undefined; - } - return api.getToken( tokenType, params.assert ).then( function ( token ) { params.token = token; return api.post( params, ajaxOptions ).then( diff --git a/resources/src/mediawiki.api/mediawiki.api.parse.js b/resources/src/mediawiki.api/mediawiki.api.parse.js index b1f1d2b06f..2dcf8078fe 100644 --- a/resources/src/mediawiki.api/mediawiki.api.parse.js +++ b/resources/src/mediawiki.api/mediawiki.api.parse.js @@ -8,31 +8,21 @@ * Convenience method for 'action=parse'. * * @param {string} wikitext - * @param {Function} [ok] Success callback (deprecated) - * @param {Function} [err] Error callback (deprecated) * @return {jQuery.Promise} * @return {Function} return.done * @return {string} return.done.data Parsed HTML of `wikitext`. */ - parse: function ( wikitext, ok, err ) { + parse: function ( wikitext ) { var apiPromise = this.get( { action: 'parse', contentmodel: 'wikitext', text: wikitext } ); - // Backwards compatibility (< MW 1.20) - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( 'Use of mediawiki.api callback params is deprecated. Use the Promise instead.' ); - } - return apiPromise .then( function ( data ) { return data.parse.text['*']; } ) - .done( ok ) - .fail( err ) .promise( { abort: apiPromise.abort } ); } } ); diff --git a/resources/src/mediawiki.api/mediawiki.api.watch.js b/resources/src/mediawiki.api/mediawiki.api.watch.js index af2dee10ad..40ba136d3f 100644 --- a/resources/src/mediawiki.api/mediawiki.api.watch.js +++ b/resources/src/mediawiki.api/mediawiki.api.watch.js @@ -12,8 +12,6 @@ * @param {string|mw.Title|string[]|mw.Title[]} pages Full page name or instance of mw.Title, or an * array thereof. If an array is passed, the return value passed to the promise will also be an * array of appropriate objects. - * @param {Function} [ok] Success callback (deprecated) - * @param {Function} [err] Error callback (deprecated) * @return {jQuery.Promise} * @return {Function} return.done * @return {Object|Object[]} return.done.watch Object or list of objects (depends on the `pages` @@ -22,7 +20,7 @@ * @return {boolean} return.done.watch.watched Whether the page is now watched or unwatched * @return {string} return.done.watch.message Parsed HTML of the confirmational interface message */ - function doWatchInternal( pages, ok, err, addParams ) { + function doWatchInternal( pages, addParams ) { // XXX: Parameter addParams is undocumented because we inherit this // documentation in the public method... var apiPromise = this.postWithToken( 'watch', @@ -36,19 +34,11 @@ ) ); - // Backwards compatibility (< MW 1.20) - if ( ok || err ) { - mw.track( 'mw.deprecate', 'api.cbParam' ); - mw.log.warn( 'Use of mediawiki.api callback params is deprecated. Use the Promise instead.' ); - } - return apiPromise .then( function ( data ) { // If a single page was given (not an array) respond with a single item as well. return $.isArray( pages ) ? data.watch : data.watch[0]; } ) - .done( ok ) - .fail( err ) .promise( { abort: apiPromise.abort } ); } @@ -58,16 +48,17 @@ * * @inheritdoc #doWatchInternal */ - watch: function ( pages, ok, err ) { - return doWatchInternal.call( this, pages, ok, err ); + watch: function ( pages ) { + return doWatchInternal.call( this, pages ); }, + /** * Convenience method for `action=watch&unwatch=1`. * * @inheritdoc #doWatchInternal */ - unwatch: function ( pages, ok, err ) { - return doWatchInternal.call( this, pages, ok, err, { unwatch: 1 } ); + unwatch: function ( pages ) { + return doWatchInternal.call( this, pages, { unwatch: 1 } ); } } ); diff --git a/resources/src/mediawiki.language/mediawiki.language.js b/resources/src/mediawiki.language/mediawiki.language.js index d4f3c69e0a..dceae1178a 100644 --- a/resources/src/mediawiki.language/mediawiki.language.js +++ b/resources/src/mediawiki.language/mediawiki.language.js @@ -40,39 +40,18 @@ $.extend( mw.language, { * * @param {number} count Non-localized quantifier * @param {Array} forms List of plural forms + * @param {Object} [explicitPluralForms] List of explicit plural forms * @return {string} Correct form for quantifier in this language */ - convertPlural: function ( count, forms ) { + convertPlural: function ( count, forms, explicitPluralForms ) { var pluralRules, - formCount, - form, - index, - equalsPosition, pluralFormIndex = 0; - if ( !forms || forms.length === 0 ) { - return ''; + if ( explicitPluralForms && explicitPluralForms[count] ) { + return explicitPluralForms[count]; } - // Handle for explicit n= forms - for ( index = 0; index < forms.length; index++ ) { - form = forms[index]; - if ( /^\d+=/.test( form ) ) { - equalsPosition = form.indexOf( '=' ); - formCount = parseInt( form.slice( 0, equalsPosition ), 10 ); - if ( formCount === count ) { - return form.slice( equalsPosition + 1 ); - } - forms[index] = undefined; - } - } - - // Remove explicit plural forms from the forms. - forms = $.map( forms, function ( form ) { - return form; - } ); - - if ( forms.length === 0 ) { + if ( !forms || forms.length === 0 ) { return ''; } diff --git a/resources/src/mediawiki.page/mediawiki.page.image.pagination.js b/resources/src/mediawiki.page/mediawiki.page.image.pagination.js index 622e818dc8..26c32a5fa3 100644 --- a/resources/src/mediawiki.page/mediawiki.page.image.pagination.js +++ b/resources/src/mediawiki.page/mediawiki.page.image.pagination.js @@ -2,23 +2,65 @@ * Implement AJAX navigation for multi-page images so the user may browse without a full page reload. */ ( function ( mw, $ ) { - var jqXhr, $multipageimage, $spinner; + var jqXhr, $multipageimage, $spinner, + cache = {}, cacheOrder = []; - /* Fetch the next page and use jQuery to swap the table.multipageimage contents. + /* Fetch the next page, caching up to 10 last-loaded pages. * @param {string} url - * @param {boolean} [hist=false] Whether this is a load triggered by history navigation (if - * true, this function won't push a new history state, for the browser did so already). + * @return {jQuery.Promise} */ - function loadPage( url, hist ) { - var $tr; - if ( jqXhr ) { + function fetchPageData( url ) { + if ( jqXhr && jqXhr.abort ) { // Prevent race conditions and piling up pending requests jqXhr.abort(); - jqXhr = undefined; } + jqXhr = undefined; + + // Try the cache + if ( cache[url] ) { + // Update access freshness + cacheOrder.splice( $.inArray( url, cacheOrder ), 1 ); + cacheOrder.push( url ); + return $.Deferred().resolve( cache[url] ).promise(); + } + + // @todo Don't fetch the entire page. Ideally we'd only fetch the content portion or the data + // (thumbnail urls) and update the interface manually. + jqXhr = $.ajax( url ).then( function ( data ) { + return $( data ).find( 'table.multipageimage' ).contents(); + } ); - // Add a new spinner if one doesn't already exist - if ( !$spinner ) { + // Handle cache updates + jqXhr.done( function ( $contents ) { + jqXhr = undefined; + + // Cache the newly loaded page + cache[url] = $contents; + cacheOrder.push( url ); + + // Remove the oldest entry if we're over the limit + if ( cacheOrder.length > 10 ) { + delete cache[ cacheOrder[0] ]; + cacheOrder = cacheOrder.slice( 1 ); + } + } ); + + return jqXhr.promise(); + } + + /* Fetch the next page and use jQuery to swap the table.multipageimage contents. + * @param {string} url + * @param {boolean} [hist=false] Whether this is a load triggered by history navigation (if + * true, this function won't push a new history state, for the browser did so already). + */ + function switchPage( url, hist ) { + var $tr, promise; + + // Start fetching data (might be cached) + promise = fetchPageData( url ); + + // Add a new spinner if one doesn't already exist and the data is not already ready + if ( !$spinner && promise.state() !== 'resolved' ) { $tr = $multipageimage.find( 'tr' ); $spinner = $.createSpinner( { size: 'large', @@ -34,13 +76,11 @@ $multipageimage.empty().append( $spinner ); } - // @todo Don't fetch the entire page. Ideally we'd only fetch the content portion or the data - // (thumbnail urls) and update the interface manually. - jqXhr = $.ajax( url ).done( function ( data ) { - jqXhr = $spinner = undefined; + promise.done( function ( $contents ) { + $spinner = undefined; // Replace table contents - $multipageimage.empty().append( $( data ).find( 'table.multipageimage' ).contents() ); + $multipageimage.empty().append( $contents.clone() ); bindPageNavigation( $multipageimage ); @@ -66,12 +106,12 @@ .extend( { title: mw.config.get( 'wgPageName' ), page: page } ) .toString(); - loadPage( uri ); + switchPage( uri ); e.preventDefault(); } ); $container.find( 'form[name="pageselector"]' ).one( 'change submit', function ( e ) { - loadPage( this.action + '?' + $( this ).serialize() ); + switchPage( this.action + '?' + $( this ).serialize() ); e.preventDefault(); } ); } @@ -93,7 +133,7 @@ $( window ).on( 'popstate', function ( e ) { var state = e.originalEvent.state; if ( state && state.tag === 'mw-pagination' ) { - loadPage( location.href, true ); + switchPage( location.href, true ); } } ); } diff --git a/resources/src/mediawiki.skinning/content.externallinks.css b/resources/src/mediawiki.skinning/content.externallinks.css old mode 100755 new mode 100644 diff --git a/resources/src/mediawiki.skinning/elements.css b/resources/src/mediawiki.skinning/elements.css old mode 100644 new mode 100755 index 392a2a6622..11962f8d8c --- a/resources/src/mediawiki.skinning/elements.css +++ b/resources/src/mediawiki.skinning/elements.css @@ -112,7 +112,7 @@ h6 { } h3 { - font-size: 132%; + font-size: 128%; } h4 { diff --git a/resources/src/mediawiki.skinning/interface.css b/resources/src/mediawiki.skinning/interface.css index 398a132dd5..b57ee367cc 100644 --- a/resources/src/mediawiki.skinning/interface.css +++ b/resources/src/mediawiki.skinning/interface.css @@ -15,6 +15,14 @@ clear: both; } +.editOptions { + background-color: #F0F0F0; + border: 1px solid silver; + border-top: none; + padding: 1em 1em 1.5em 1em; + margin-bottom: 2em; +} + .usermessage { background-color: #ffce7b; border: 1px solid #ffa500; diff --git a/resources/src/mediawiki.special/mediawiki.special.changeslist.legend.css b/resources/src/mediawiki.special/mediawiki.special.changeslist.legend.css index 6b0bf9919d..55f2f89c65 100644 --- a/resources/src/mediawiki.special/mediawiki.special.changeslist.legend.css +++ b/resources/src/mediawiki.special/mediawiki.special.changeslist.legend.css @@ -25,5 +25,9 @@ .mw-changeslist-legend dd { margin-left: 1.5em; +} + +.mw-changeslist-legend dt, +.mw-changeslist-legend dd { line-height: 1.3em; } diff --git a/resources/src/mediawiki.special/mediawiki.special.css b/resources/src/mediawiki.special/mediawiki.special.css index 0356fc741e..d245726222 100644 --- a/resources/src/mediawiki.special/mediawiki.special.css +++ b/resources/src/mediawiki.special/mediawiki.special.css @@ -118,3 +118,8 @@ table.mw-userrights-groups * td, table.mw-userrights-groups * th { padding-right: 1.5em; } + +/* Special:Contributions */ +.mw-contributions-form select { + vertical-align: middle; +} diff --git a/resources/src/mediawiki.special/mediawiki.special.upload.js b/resources/src/mediawiki.special/mediawiki.special.upload.js index 286befcc6d..04bc97879d 100644 --- a/resources/src/mediawiki.special/mediawiki.special.upload.js +++ b/resources/src/mediawiki.special/mediawiki.special.upload.js @@ -562,4 +562,30 @@ } } ); + $( function () { + // Prevent losing work + var allowCloseWindow, + $uploadForm = $( '#mw-upload-form' ); + + if ( !mw.user.options.get( 'useeditwarning' ) ) { + // If the user doesn't want edit warnings, don't set things up. + return; + } + + $uploadForm.data( 'origtext', $uploadForm.serialize() ); + + allowCloseWindow = mw.confirmCloseWindow( { + test: function () { + return $( '#wpUploadFile' ).get( 0 ).files.length !== 0 || + $uploadForm.data( 'origtext' ) !== $uploadForm.serialize(); + }, + + message: mw.msg( 'editwarning-warning' ), + namespace: 'uploadwarning' + } ); + + $uploadForm.submit( function () { + allowCloseWindow(); + } ); + } ); }( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.special/mediawiki.special.version.css b/resources/src/mediawiki.special/mediawiki.special.version.css index 764e37777f..7c87d68f8e 100644 --- a/resources/src/mediawiki.special/mediawiki.special.version.css +++ b/resources/src/mediawiki.special/mediawiki.special.version.css @@ -12,3 +12,7 @@ th.mw-version-ext-col-label { font-size: 0.9em; } + +.mw-version-ext-vcs-version { + unicode-bidi: embed; +} diff --git a/resources/src/mediawiki.toolbar/images/ar/button_bold.png b/resources/src/mediawiki.toolbar/images/ar/button_bold.png new file mode 100644 index 0000000000..e524f6cbad Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ar/button_bold.png differ diff --git a/resources/src/mediawiki.toolbar/images/ar/button_headline.png b/resources/src/mediawiki.toolbar/images/ar/button_headline.png new file mode 100644 index 0000000000..398e5614fe Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ar/button_headline.png differ diff --git a/resources/src/mediawiki.toolbar/images/ar/button_italic.png b/resources/src/mediawiki.toolbar/images/ar/button_italic.png new file mode 100644 index 0000000000..6ec73e9e32 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ar/button_italic.png differ diff --git a/resources/src/mediawiki.toolbar/images/ar/button_link.png b/resources/src/mediawiki.toolbar/images/ar/button_link.png new file mode 100644 index 0000000000..c9c63f6c56 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ar/button_link.png differ diff --git a/resources/src/mediawiki.toolbar/images/ar/button_nowiki.png b/resources/src/mediawiki.toolbar/images/ar/button_nowiki.png new file mode 100644 index 0000000000..743ea61b1e Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ar/button_nowiki.png differ diff --git a/resources/src/mediawiki.toolbar/images/be-tarask/button_bold.png b/resources/src/mediawiki.toolbar/images/be-tarask/button_bold.png new file mode 100644 index 0000000000..5c10cfe205 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/be-tarask/button_bold.png differ diff --git a/resources/src/mediawiki.toolbar/images/be-tarask/button_italic.png b/resources/src/mediawiki.toolbar/images/be-tarask/button_italic.png new file mode 100644 index 0000000000..72209d74ba Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/be-tarask/button_italic.png differ diff --git a/resources/src/mediawiki.toolbar/images/be-tarask/button_link.png b/resources/src/mediawiki.toolbar/images/be-tarask/button_link.png new file mode 100644 index 0000000000..09c86fb14a Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/be-tarask/button_link.png differ diff --git a/resources/src/mediawiki.toolbar/images/de/button_bold.png b/resources/src/mediawiki.toolbar/images/de/button_bold.png new file mode 100644 index 0000000000..367d5bc149 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/de/button_bold.png differ diff --git a/resources/src/mediawiki.toolbar/images/de/button_italic.png b/resources/src/mediawiki.toolbar/images/de/button_italic.png new file mode 100644 index 0000000000..fdd8c9f924 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/de/button_italic.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_bold.png b/resources/src/mediawiki.toolbar/images/en/button_bold.png new file mode 100644 index 0000000000..75c3f1094a Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_bold.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_extlink.png b/resources/src/mediawiki.toolbar/images/en/button_extlink.png new file mode 100644 index 0000000000..458943c1d1 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_extlink.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_headline.png b/resources/src/mediawiki.toolbar/images/en/button_headline.png new file mode 100644 index 0000000000..9cf751d9d2 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_headline.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_hr.png b/resources/src/mediawiki.toolbar/images/en/button_hr.png new file mode 100644 index 0000000000..47e1ca4013 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_hr.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_image.png b/resources/src/mediawiki.toolbar/images/en/button_image.png new file mode 100644 index 0000000000..69192965c4 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_image.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_italic.png b/resources/src/mediawiki.toolbar/images/en/button_italic.png new file mode 100644 index 0000000000..527fbd1455 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_italic.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_link.png b/resources/src/mediawiki.toolbar/images/en/button_link.png new file mode 100644 index 0000000000..eb5634b910 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_link.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_media.png b/resources/src/mediawiki.toolbar/images/en/button_media.png new file mode 100644 index 0000000000..4194ec1875 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_media.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_nowiki.png b/resources/src/mediawiki.toolbar/images/en/button_nowiki.png new file mode 100644 index 0000000000..2ba818de2b Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_nowiki.png differ diff --git a/resources/src/mediawiki.toolbar/images/en/button_sig.png b/resources/src/mediawiki.toolbar/images/en/button_sig.png new file mode 100644 index 0000000000..fe34b3fb9a Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/en/button_sig.png differ diff --git a/resources/src/mediawiki.toolbar/images/fa/button_bold.png b/resources/src/mediawiki.toolbar/images/fa/button_bold.png new file mode 100644 index 0000000000..c54d094cad Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/fa/button_bold.png differ diff --git a/resources/src/mediawiki.toolbar/images/fa/button_headline.png b/resources/src/mediawiki.toolbar/images/fa/button_headline.png new file mode 100644 index 0000000000..9890d15504 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/fa/button_headline.png differ diff --git a/resources/src/mediawiki.toolbar/images/fa/button_italic.png b/resources/src/mediawiki.toolbar/images/fa/button_italic.png new file mode 100644 index 0000000000..33f91ed613 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/fa/button_italic.png differ diff --git a/resources/src/mediawiki.toolbar/images/fa/button_link.png b/resources/src/mediawiki.toolbar/images/fa/button_link.png new file mode 100644 index 0000000000..76b939e6af Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/fa/button_link.png differ diff --git a/resources/src/mediawiki.toolbar/images/fa/button_nowiki.png b/resources/src/mediawiki.toolbar/images/fa/button_nowiki.png new file mode 100644 index 0000000000..743ea61b1e Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/fa/button_nowiki.png differ diff --git a/resources/src/mediawiki.toolbar/images/ksh/LICENSE b/resources/src/mediawiki.toolbar/images/ksh/LICENSE new file mode 100644 index 0000000000..47ecfe4ed2 --- /dev/null +++ b/resources/src/mediawiki.toolbar/images/ksh/LICENSE @@ -0,0 +1,7 @@ + +button_italic.png +------------------- +Source : http://commons.wikimedia.org/wiki/Image:Button_S_italic.png +License: Public domain +Author : Purodha Blissenbach, http://ksh.wikipedia.org/wiki/User:Purodha + diff --git a/resources/src/mediawiki.toolbar/images/ksh/button_italic.png b/resources/src/mediawiki.toolbar/images/ksh/button_italic.png new file mode 100644 index 0000000000..15496c082c Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ksh/button_italic.png differ diff --git a/resources/src/mediawiki.toolbar/images/ru/LICENSE b/resources/src/mediawiki.toolbar/images/ru/LICENSE new file mode 100644 index 0000000000..bedcec6621 --- /dev/null +++ b/resources/src/mediawiki.toolbar/images/ru/LICENSE @@ -0,0 +1,17 @@ +button_bold.png +--------------- +Source : http://commons.wikimedia.org/wiki/Image:Button_bold_ukr.png +License: Public domain +Author : Alexey Belomoev + +button_italic.png +------------------------ +Source : http://commons.wikimedia.org/wiki/Image:Button_italic_ukr.png +License: Public domain +Author : Alexey Belomoev + +button_link.png +----------------- +Source : http://commons.wikimedia.org/wiki/Image:Button_internal_link_ukr.png +License: GPL +Author : Saproj, Erik Möller diff --git a/resources/src/mediawiki.toolbar/images/ru/button_bold.png b/resources/src/mediawiki.toolbar/images/ru/button_bold.png new file mode 100644 index 0000000000..eae30d98e5 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ru/button_bold.png differ diff --git a/resources/src/mediawiki.toolbar/images/ru/button_italic.png b/resources/src/mediawiki.toolbar/images/ru/button_italic.png new file mode 100644 index 0000000000..b958d2205c Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ru/button_italic.png differ diff --git a/resources/src/mediawiki.toolbar/images/ru/button_link.png b/resources/src/mediawiki.toolbar/images/ru/button_link.png new file mode 100644 index 0000000000..12ad373192 Binary files /dev/null and b/resources/src/mediawiki.toolbar/images/ru/button_link.png differ diff --git a/resources/src/mediawiki.toolbar/toolbar.js b/resources/src/mediawiki.toolbar/toolbar.js new file mode 100644 index 0000000000..f9944b48d1 --- /dev/null +++ b/resources/src/mediawiki.toolbar/toolbar.js @@ -0,0 +1,202 @@ +/** + * Interface for the classic edit toolbar. + * + * @class mw.toolbar + * @singleton + */ +( function ( mw, $ ) { + var toolbar, isReady, $toolbar, queue, slice, $currentFocused; + + /** + * Internal helper that does the actual insertion of the button into the toolbar. + * + * For backwards-compatibility, passing `imageFile`, `speedTip`, `tagOpen`, `tagClose`, + * `sampleText` and `imageId` as separate arguments (in this order) is also supported. + * + * @private + * + * @param {Object} button Object with the following properties. + * You are required to provide *either* the `onClick` parameter, or the three parameters + * `tagOpen`, `tagClose` and `sampleText`, but not both (they're mutually exclusive). + * @param {string} [button.imageFile] Image to use for the button. + * @param {string} button.speedTip Tooltip displayed when user mouses over the button. + * @param {Function} [button.onClick] Function to be executed when the button is clicked. + * @param {string} [button.tagOpen] + * @param {string} [button.tagClose] + * @param {string} [button.sampleText] Alternative to `onClick`. `tagOpen`, `tagClose` and + * `sampleText` together provide the markup that should be inserted into page text at + * current cursor position. + * @param {string} [button.imageId] `id` attribute of the button HTML element. Can be + * used to define the image with CSS if it's not provided as `imageFile`. + */ + function insertButton( button, speedTip, tagOpen, tagClose, sampleText, imageId ) { + var $button; + + // Backwards compatibility + if ( typeof button !== 'object' ) { + button = { + imageFile: button, + speedTip: speedTip, + tagOpen: tagOpen, + tagClose: tagClose, + sampleText: sampleText, + imageId: imageId + }; + } + + if ( button.imageFile ) { + $button = $( '' ).attr( { + src: button.imageFile, + alt: button.speedTip, + title: button.speedTip, + id: button.imageId || undefined, + 'class': 'mw-toolbar-editbutton' + } ); + } else { + $button = $( '
' ).attr( { + title: button.speedTip, + id: button.imageId || undefined, + 'class': 'mw-toolbar-editbutton' + } ); + } + + $button.click( function ( e ) { + if ( button.onClick !== undefined ) { + button.onClick( e ); + } else { + toolbar.insertTags( button.tagOpen, button.tagClose, button.sampleText ); + } + + return false; + } ); + + $toolbar.append( $button ); + } + + isReady = false; + $toolbar = false; + + /** + * @private + * @property {Array} + * Contains button objects (and for backwards compatibilty, it can + * also contains an arguments array for insertButton). + */ + queue = []; + slice = queue.slice; + + toolbar = { + + /** + * Add buttons to the toolbar. + * + * Takes care of race conditions and time-based dependencies by placing buttons in a queue if + * this method is called before the toolbar is created. + * + * For backwards-compatibility, passing `imageFile`, `speedTip`, `tagOpen`, `tagClose`, + * `sampleText` and `imageId` as separate arguments (in this order) is also supported. + * + * @inheritdoc #insertButton + */ + addButton: function () { + if ( isReady ) { + insertButton.apply( toolbar, arguments ); + } else { + // Convert arguments list to array + queue.push( slice.call( arguments ) ); + } + }, + + /** + * Add multiple buttons to the toolbar (see also #addButton). + * + * Example usage: + * + * addButtons( [ { .. }, { .. }, { .. } ] ); + * addButtons( { .. }, { .. } ); + * + * @param {Object|Array...} [buttons] An array of button objects or the first + * button object in a list of variadic arguments. + */ + addButtons: function ( buttons ) { + if ( !$.isArray( buttons ) ) { + buttons = slice.call( arguments ); + } + if ( isReady ) { + $.each( buttons, function () { + insertButton( this ); + } ); + } else { + // Push each button into the queue + queue.push.apply( queue, buttons ); + } + }, + + /** + * Apply tagOpen/tagClose to selection in currently focused textarea. + * + * Uses `sampleText` if selection is empty. + * + * @param {string} tagOpen + * @param {string} tagClose + * @param {string} sampleText + */ + insertTags: function ( tagOpen, tagClose, sampleText ) { + if ( $currentFocused && $currentFocused.length ) { + $currentFocused.textSelection( + 'encapsulateSelection', { + pre: tagOpen, + peri: sampleText, + post: tagClose + } + ); + } + } + }; + + // Legacy (for compatibility with the code previously in skins/common.edit.js) + mw.log.deprecate( window, 'addButton', toolbar.addButton, 'Use mw.toolbar.addButton instead.' ); + mw.log.deprecate( window, 'insertTags', toolbar.insertTags, 'Use mw.toolbar.insertTags instead.' ); + + // For backwards compatibility. Used to be called from EditPage.php, maybe other places as well. + mw.log.deprecate( toolbar, 'init', $.noop ); + + // Expose API publicly + mw.toolbar = toolbar; + + $( function () { + var i, button; + + // Used to determine where to insert tags + $currentFocused = $( '#wpTextbox1' ); + + // Populate the selector cache for $toolbar + $toolbar = $( '#toolbar' ); + + for ( i = 0; i < queue.length; i++ ) { + button = queue[i]; + if ( $.isArray( button ) ) { + // Forwarded arguments array from mw.toolbar.addButton + insertButton.apply( toolbar, button ); + } else { + // Raw object from mw.toolbar.addButtons + insertButton( button ); + } + } + + // Clear queue + queue.length = 0; + + // This causes further calls to addButton to go to insertion directly + // instead of to the queue. + // It is important that this is after the one and only loop through + // the the queue + isReady = true; + + // Apply to dynamically created textboxes as well as normal ones + $( document ).on( 'focus', 'textarea, input:text', function () { + $currentFocused = $( this ); + } ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.toolbar/toolbar.less b/resources/src/mediawiki.toolbar/toolbar.less new file mode 100644 index 0000000000..d65b284217 --- /dev/null +++ b/resources/src/mediawiki.toolbar/toolbar.less @@ -0,0 +1,42 @@ +@import "mediawiki.mixins"; + +#mw-editbutton-bold { + .background-image("images/@{button-bold}"); +} + +#mw-editbutton-italic { + .background-image("images/@{button-italic}"); +} + +#mw-editbutton-link { + .background-image("images/@{button-link}"); +} + +#mw-editbutton-extlink { + .background-image("images/@{button-extlink}"); +} + +#mw-editbutton-headline { + .background-image("images/@{button-headline}"); +} + +#mw-editbutton-image { + .background-image("images/@{button-image}"); +} + +#mw-editbutton-media { + .background-image("images/@{button-media}"); +} + +#mw-editbutton-nowiki { + .background-image("images/@{button-nowiki}"); +} + +// Who decided to make only this single one different than the name of the data item? +#mw-editbutton-signature { + .background-image("images/@{button-sig}"); +} + +#mw-editbutton-hr { + .background-image("images/@{button-hr}"); +} diff --git a/resources/src/mediawiki.ui/components/anchors.less b/resources/src/mediawiki.ui/components/anchors.less index e1b258dda8..f0fb7b95b3 100644 --- a/resources/src/mediawiki.ui/components/anchors.less +++ b/resources/src/mediawiki.ui/components/anchors.less @@ -3,13 +3,8 @@ @import "mediawiki.ui/mixins"; // Helpers -.mw-ui-anchor( @mainColor ) { - // Make all context classes take the main color in IE6 - .select-ie6-only& { - &:link, &:visited, &:hover, &:focus, &:active { - color: @mainColor; - } - } +.mixin-mw-ui-anchor-styles( @mainColor ) { + color: @mainColor; // Hover state &:hover { @@ -21,57 +16,65 @@ outline: none; // outline fix } - color: @mainColor; - // Quiet mode is gray at first &.mw-ui-quiet { - .mw-ui-anchor-quiet( @mainColor ); + .mixin-mw-ui-anchor-styles-quiet( @mainColor ); } } -.mw-ui-anchor-quiet( @mainColor ) { - color: @colorTextLight; - text-decoration: none; +/* +Anchors - &:hover { - color: @mainColor; - } - &:focus, &:active { - color: darken( @mainColor, @colorDarkenPercentage ); - } -} +The anchor base type can be applied to A elements when a basic context styling needs to be given to a link, without +having to assign it as a button type. mw-ui-anchor only changes the text color, and should not be used in combination +with other base classes, such as mw-ui-button. -/* -Text & Anchors +Markup: +Progressive +Constructive +Destructive -Allows you to give text a context as to the type of action it is indicating. +.mw-ui-quiet - Quiet until interaction. -Styleguide 6. +Styleguide 6.2. */ +// Setup compound anchor selectors (such as .mw-ui-anchor.mw-ui-progressive) +.mw-ui-anchor { + &.mw-ui-progressive { + .mixin-mw-ui-anchor-styles( @colorProgressive ); + } + + &.mw-ui-constructive { + .mixin-mw-ui-anchor-styles( @colorConstructive ); + } + + &.mw-ui-destructive { + .mixin-mw-ui-anchor-styles( @colorDestructive ); + } +} + /* -Guidelines +Quiet anchors -This context should only applied on elements without special behavior (DIV, SPAN, etc.), including A elements. These classes cannot be applied for styling purposes on other elements (such as form elements), except when used in combination with .mw-ui-button to alter a button context. +Use quiet anchors when they are less important and alongside other progressive/destructive/progressive +anchors. Use of quiet anchors is not recommended on mobile/tablet due to lack of hover state. Markup: -Progressive -Constructive -Destructive +Progressive +Constructive +Destructive -.mw-ui-quiet - Quiet until interaction. - -Styleguide 6.1. +Styleguide 6.2.1. */ -.mw-ui-progressive { - .mw-ui-anchor( @colorProgressive ); -} -.mw-ui-constructive { - .mw-ui-anchor( @colorConstructive ); -} -.mw-ui-destructive { - .mw-ui-anchor( @colorDestructive ); -} -.mw-ui-quiet { - .mw-ui-anchor-quiet( @colorTextLight ); +.mixin-mw-ui-anchor-styles-quiet( @mainColor ) { + color: @colorTextLight; + text-decoration: none; + + &:hover { + color: @mainColor; + } + &:focus, &:active { + color: darken( @mainColor, @colorDarkenPercentage ); + } } diff --git a/resources/src/mediawiki.ui/components/buttons.less b/resources/src/mediawiki.ui/components/buttons.less index 3d60b7a4e1..2512d49270 100644 --- a/resources/src/mediawiki.ui/components/buttons.less +++ b/resources/src/mediawiki.ui/components/buttons.less @@ -47,7 +47,7 @@ // Container styling .button-colors(#FFF); border-radius: @borderRadius; - min-width: 70px; + min-width: 4em; // Ensure that buttons and inputs are nicely aligned when they have differing heights vertical-align: middle; @@ -188,7 +188,8 @@ // Quiet buttons // - // Use quiet buttons when they are less important and alongisde other progressive/destructive/progressive buttons. + // Use quiet buttons when they are less important and alongside other constructive/progressive/destructive buttons. + // Use of quiet buttons is not recommended on mobile/tablet due to lack of hover state. // // Markup: //
@@ -257,6 +258,7 @@ a.mw-ui-button { // // Styleguide 2.2. .mw-ui-button-group > * { + min-width: 48px; border-radius: 0; float: left; diff --git a/resources/src/mediawiki.ui/components/checkbox.less b/resources/src/mediawiki.ui/components/checkbox.less index 1997c1b5d1..4204c29918 100644 --- a/resources/src/mediawiki.ui/components/checkbox.less +++ b/resources/src/mediawiki.ui/components/checkbox.less @@ -14,7 +14,13 @@ // //
//
-// +// +//
+//
+// +//
+//
+// //
// // Styleguide 5. @@ -42,6 +48,8 @@ // ensure the invisible checkbox takes up the required width width: @checkboxSize; height: @checkboxSize; + // This is needed for Firefox mobile (See bug 71750 to workaround default Firefox stylesheet) + max-width: none; // the pseudo before element of the label after the checkbox now looks like a checkbox & + label { @@ -74,7 +82,7 @@ } } - @focusBottomBorderSize: 3px; + @focusBottomBorderSize: 0.2em; &:active, &:focus { + label { @@ -82,7 +90,7 @@ content: ''; position: absolute; width: @checkboxSize; - height: @checkboxSize - @focusBottomBorderSize + 1; // offset by bottom border + height: @checkboxSize - @focusBottomBorderSize + 0.08; // offset by bottom border // offset from the checkbox by 1px to account for left border left: 1px; border-bottom: solid @focusBottomBorderSize lightgrey; diff --git a/resources/src/mediawiki.ui/components/icons.less b/resources/src/mediawiki.ui/components/icons.less index 3a92b21a2b..d85cc98361 100644 --- a/resources/src/mediawiki.ui/components/icons.less +++ b/resources/src/mediawiki.ui/components/icons.less @@ -16,13 +16,19 @@ // To use icons you must be using a browser that supports pseudo elements. // This includes support for IE8. // http://caniuse.com/#feat=css-gencontent -// Browsers that do not support either of these selectors will degrade to text only. +// +// For elements that are intended to have both an icon and text, browsers that +// do not support pseudo-selectors will degrade to text-only. +// +// However, icon-only elements do not yet degrade to text-only elements in these +// browsers. // // Styleguide 4. .mw-ui-icon { position: relative; min-height: @iconSize; + min-width: @iconSize; // Standalone icons // diff --git a/resources/src/mediawiki.ui/components/inputs.less b/resources/src/mediawiki.ui/components/inputs.less index 06a4ccdf5e..685ca4d9c9 100644 --- a/resources/src/mediawiki.ui/components/inputs.less +++ b/resources/src/mediawiki.ui/components/inputs.less @@ -129,3 +129,11 @@ input.mw-ui-input-large { font-weight: bold; line-height: 1.25em; } + +// Tablet and desktop specific styling tweaks. +@media all and (min-width: 768px) { + // Make inline elements take up a sensible amount of the screen on wider devices. + .mw-ui-input-inline { + min-width: 320px; + } +} diff --git a/resources/src/mediawiki.ui/components/text.less b/resources/src/mediawiki.ui/components/text.less new file mode 100644 index 0000000000..500d42c4a4 --- /dev/null +++ b/resources/src/mediawiki.ui/components/text.less @@ -0,0 +1,40 @@ +@import "mediawiki.mixins"; +@import "mediawiki.ui/variables"; +@import "mediawiki.ui/mixins"; + +/* +Text & Anchors + +Allows you to give text a context as to the type of action it is indicating. + +Styleguide 6. +*/ + +/* +Text + +Context classes may be used on elements with only plain-text content with the mw-ui-text base. When the context classes +are used on interactive and block-level elements, the appropriate alternative base type classes should also be used. For +example, mw-ui-anchor with A, or mw-ui-button with buttons. + +Markup: +Progressive +Constructive +Destructive + +Styleguide 6.1. +*/ + +.mw-ui-text { + // The selector order is like this on purpose; IE6 ignores the second selector, + // so we don't want to accidentally apply this color on all mw-ui-CONTEXT classes + .mw-ui-progressive& { + color: @colorProgressive; + } + .mw-ui-constructive& { + color: @colorConstructive; + } + .mw-ui-destructive& { + color: @colorDestructive; + } +} \ No newline at end of file diff --git a/resources/src/mediawiki/mediawiki.confirmCloseWindow.js b/resources/src/mediawiki/mediawiki.confirmCloseWindow.js new file mode 100644 index 0000000000..177367da93 --- /dev/null +++ b/resources/src/mediawiki/mediawiki.confirmCloseWindow.js @@ -0,0 +1,64 @@ +( function ( mw, $ ) { + /** + * @method confirmCloseWindow + * @member mw + * + * Prevent the closing of a window with a confirm message (the onbeforeunload event seems to + * work in most browsers.) + * + * This supersedes any previous onbeforeunload handler. If there was a handler before, it is + * restored when you execute the returned function. + * + * var allowCloseWindow = mw.confirmCloseWindow(); + * // ... do stuff that can't be interrupted ... + * allowCloseWindow(); + * + * @param {Object} [options] + * @param {string} [options.namespace] Namespace for the event registration + * @param {string} [options.message] + * @param {string} options.message.return The string message to show in the confirm dialog. + * @param {Function} [options.test] + * @param {boolean} [options.test.return=true] Whether to show the dialog to the user. + * @return {Function} Execute this when you want to allow the user to close the window + */ + mw.confirmCloseWindow = function ( options ) { + var savedUnloadHandler, + mainEventName = 'beforeunload', + showEventName = 'pageshow'; + + options = $.extend( { + message: mw.message( 'mwe-prevent-close' ).text(), + test: function () { return true; } + }, options ); + + if ( options.namespace ) { + mainEventName += '.' + options.namespace; + showEventName += '.' + options.namespace; + } + + $( window ).on( mainEventName, function () { + if ( options.test() ) { + // remove the handler while the alert is showing - otherwise breaks caching in Firefox (3?). + // but if they continue working on this page, immediately re-register this handler + savedUnloadHandler = window.onbeforeunload; + window.onbeforeunload = null; + setTimeout( function () { + window.onbeforeunload = savedUnloadHandler; + }, 1 ); + + // show an alert with this message + return options.message; + } + } ).on( showEventName, function () { + // Re-add onbeforeunload handler + if ( !window.onbeforeunload && savedUnloadHandler ) { + window.onbeforeunload = savedUnloadHandler; + } + } ); + + // return the function they can use to stop this + return function () { + $( window ).off( mainEventName + ' ' + showEventName ); + }; + }; +} )( mediaWiki, jQuery ); diff --git a/resources/src/mediawiki/mediawiki.jqueryMsg.js b/resources/src/mediawiki/mediawiki.jqueryMsg.js index ad71b08384..3eaa6d2cd1 100644 --- a/resources/src/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/src/mediawiki/mediawiki.jqueryMsg.js @@ -1129,17 +1129,42 @@ * @return {string} selected pluralized form according to current language */ plural: function ( nodes ) { - var forms, formIndex, node, count; + var forms, firstChild, firstChildText, + explicitPluralForms = {}, explicitPluralFormNumber, formIndex, form, count; + count = parseFloat( this.language.convertNumber( nodes[0], true ) ); forms = nodes.slice( 1 ); for ( formIndex = 0; formIndex < forms.length; formIndex++ ) { - node = forms[formIndex]; - if ( node.jquery && node.hasClass( 'mediaWiki_htmlEmitter' ) ) { - // This is a nested node, already expanded. - forms[formIndex] = forms[formIndex].html(); + form = forms[formIndex]; + + if ( form.jquery && form.hasClass( 'mediaWiki_htmlEmitter' ) ) { + // This is a nested node, may be an explicit plural form like 5=[$2 linktext] + firstChild = form.contents().get( 0 ); + if ( firstChild && firstChild.nodeType === Node.TEXT_NODE ) { + firstChildText = firstChild.textContent; + if ( /^\d+=/.test( firstChildText ) ) { + explicitPluralFormNumber = parseInt( firstChildText.split( /=/ )[0], 10 ); + // Use the digit part as key and rest of first text node and + // rest of child nodes as value. + firstChild.textContent = firstChildText.slice( firstChildText.indexOf( '=' ) + 1 ); + explicitPluralForms[explicitPluralFormNumber] = form; + forms[formIndex] = undefined; + } + } + } else if ( /^\d+=/.test( form ) ) { + // Simple explicit plural forms like 12=a dozen + explicitPluralFormNumber = parseInt( form.split( /=/ )[0], 10 ); + explicitPluralForms[explicitPluralFormNumber] = form.slice( form.indexOf( '=' ) + 1 ); + forms[formIndex] = undefined; } } - return forms.length ? this.language.convertPlural( count, forms ) : ''; + + // Remove explicit plural forms from the forms. They were set undefined in the above loop. + forms = $.map( forms, function ( form ) { + return form; + } ); + + return this.language.convertPlural( count, forms, explicitPluralForms ); }, /** diff --git a/resources/src/mediawiki/mediawiki.user.js b/resources/src/mediawiki/mediawiki.user.js index e93707ecdf..809a65eea6 100644 --- a/resources/src/mediawiki/mediawiki.user.js +++ b/resources/src/mediawiki/mediawiki.user.js @@ -241,18 +241,4 @@ } }; - /** - * @method name - * @inheritdoc #getName - * @deprecated since 1.20 Use #getName instead - */ - mw.log.deprecate( user, 'name', user.getName, 'Use mw.user.getName instead.' ); - - /** - * @method anonymous - * @inheritdoc #isAnon - * @deprecated since 1.20 Use #isAnon instead - */ - mw.log.deprecate( user, 'anonymous', user.isAnon, 'Use mw.user.isAnon instead.' ); - }( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/mediawiki.userSuggest.js b/resources/src/mediawiki/mediawiki.userSuggest.js new file mode 100644 index 0000000000..aed093caa8 --- /dev/null +++ b/resources/src/mediawiki/mediawiki.userSuggest.js @@ -0,0 +1,42 @@ +/*! + * Add autocomplete suggestions for names of registered users. + */ +( function ( mw, $ ) { + var api, config; + + config = { + fetch: function ( userInput ) { + var $textbox = this, + node = this[0]; + + api = api || new mw.Api(); + + $.data( node, 'request', api.get( { + action: 'query', + list: 'allusers', + // Prefix of list=allusers is case sensitive. Normalise first + // character to uppercase so that "fo" may yield "Foo". + auprefix: userInput.charAt( 0 ).toUpperCase() + userInput.slice( 1 ) + } ).done( function ( data ) { + var users = $.map( data.query.allusers, function ( userObj ) { + return userObj.name; + } ); + // Set the results as the autocomplete options + $textbox.suggestions( 'suggestions', users ); + } ) ); + }, + cancel: function () { + var node = this[0], + request = $.data( node, 'request' ); + + if ( request ) { + request.abort(); + $.removeData( node, 'request' ); + } + } + }; + + $( function () { + $( '.mw-autocomplete-user' ).suggestions( config ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/mediawiki.util.js b/resources/src/mediawiki/mediawiki.util.js index 2662913781..3a06a02e40 100644 --- a/resources/src/mediawiki/mediawiki.util.js +++ b/resources/src/mediawiki/mediawiki.util.js @@ -286,30 +286,38 @@ } if ( tooltip ) { - $link.attr( 'title', tooltip ).updateTooltipAccessKeys(); + $link.attr( 'title', tooltip ); } if ( nextnode ) { + // Case: nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js) + // Case: nextnode is a CSS selector for jQuery if ( nextnode.nodeType || typeof nextnode === 'string' ) { - // nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js) - // or nextnode is a CSS selector for jQuery nextnode = $ul.find( nextnode ); - } else if ( !nextnode.jquery || ( nextnode.length && nextnode[0].parentNode !== $ul[0] ) ) { - // Fallback - $ul.append( $item ); - return $item[0]; + } else if ( !nextnode.jquery ) { + // Error: Invalid nextnode + nextnode = undefined; } - if ( nextnode.length === 1 ) { - // nextnode is a jQuery object that represents exactly one element - nextnode.before( $item ); - return $item[0]; + if ( nextnode && ( nextnode.length !== 1 || nextnode[0].parentNode !== $ul[0] ) ) { + // Error: nextnode must resolve to a single node + // Error: nextnode must have the associated
    as its parent + nextnode = undefined; } } - // Fallback (this is the default behavior) - $ul.append( $item ); - return $item[0]; + // Case: nextnode is a jQuery-wrapped DOM element + if ( nextnode ) { + nextnode.before( $item ); + } else { + // Fallback (this is the default behavior) + $ul.append( $item ); + } + + // Update tooltip for the access key after inserting into DOM + // to get a localized access key label (bug 67946). + $link.updateTooltipAccessKeys(); + return $item[0]; }, /** diff --git a/tests/browser/Gemfile b/tests/browser/Gemfile old mode 100755 new mode 100644 diff --git a/tests/browser/features/create_account.feature b/tests/browser/features/create_account.feature index d6745ad75b..0b4e83a559 100644 --- a/tests/browser/features/create_account.feature +++ b/tests/browser/features/create_account.feature @@ -1,4 +1,4 @@ -@chrome @clean @en.wikipedia.beta.wmflabs.org @firefox @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @phantomjs Feature: Create account Scenario Outline: Go to Create account page diff --git a/tests/browser/features/create_and_follow_wiki_link.feature b/tests/browser/features/create_and_follow_wiki_link.feature index 29dd1c1fab..a0aa624e4b 100644 --- a/tests/browser/features/create_and_follow_wiki_link.feature +++ b/tests/browser/features/create_and_follow_wiki_link.feature @@ -1,4 +1,4 @@ -@chrome @clean @en.wikipedia.beta.wmflabs.org @firefox @login @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @login @phantomjs Feature: Create Page With Wiki Link Scenario: Create Page With Wiki Link diff --git a/tests/browser/features/edit_page.feature b/tests/browser/features/edit_page.feature index c190a78972..b905795e7e 100644 --- a/tests/browser/features/edit_page.feature +++ b/tests/browser/features/edit_page.feature @@ -1,4 +1,4 @@ -@chrome @clean @en.wikipedia.beta.wmflabs.org @firefox @login @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @login @phantomjs Feature: Edit Page Scenario: Create and edit page diff --git a/tests/browser/features/file.feature b/tests/browser/features/file.feature index 2d3b70858f..0bd36ed667 100644 --- a/tests/browser/features/file.feature +++ b/tests/browser/features/file.feature @@ -9,7 +9,7 @@ # qa-browsertests top-level directory and at # https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS # -@chrome @en.wikipedia.beta.wmflabs.org @firefox @internet_explorer_6 @internet_explorer_7 @internet_explorer_8 @internet_explorer_9 @internet_explorer_10 @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @internet_explorer_6 @internet_explorer_7 @internet_explorer_8 @internet_explorer_9 @internet_explorer_10 @phantomjs Feature: File Scenario: Anonymous goes to file that does not exist @@ -20,4 +20,4 @@ Feature: File Scenario: Logged-in user goes to file that does not exist Given I am logged in And I am at file that does not exist - Then page should show that no such file exists \ No newline at end of file + Then page should show that no such file exists diff --git a/tests/browser/features/login.feature b/tests/browser/features/login.feature index 9ff2a2a356..c34d23d353 100644 --- a/tests/browser/features/login.feature +++ b/tests/browser/features/login.feature @@ -9,7 +9,7 @@ # qa-browsertests top-level directory and at # https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS # -@chrome @en.wikipedia.beta.wmflabs.org @firefox @internet_explorer_6 @internet_explorer_7 @internet_explorer_8 @internet_explorer_9 @internet_explorer_10 @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @internet_explorer_6 @internet_explorer_7 @internet_explorer_8 @internet_explorer_9 @internet_explorer_10 @phantomjs Feature: Log in Background: @@ -39,4 +39,4 @@ Feature: Log in @login Scenario: Log in with valid credentials When I am logged in - Then error box should not be visible \ No newline at end of file + Then error box should not be visible diff --git a/tests/browser/features/main_page_links.feature b/tests/browser/features/main_page_links.feature index 561fe5355e..3613c828ae 100644 --- a/tests/browser/features/main_page_links.feature +++ b/tests/browser/features/main_page_links.feature @@ -1,4 +1,4 @@ -@chrome @clean @en.wikipedia.beta.wmflabs.org @firefox @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @phantomjs Feature: Main Page View History Links Background: @@ -16,4 +16,4 @@ Feature: Main Page View History Links And I should see a link for Special pages And I should see a link for Printable version And I should see a link for Permanent link - And I should see a link for Page information \ No newline at end of file + And I should see a link for Page information diff --git a/tests/browser/features/preferences.feature b/tests/browser/features/preferences.feature index 1a958bbb6d..9e3abfde58 100644 --- a/tests/browser/features/preferences.feature +++ b/tests/browser/features/preferences.feature @@ -9,7 +9,7 @@ # qa-browsertests top-level directory and at # https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS # -@chrome @clean @en.wikipedia.beta.wmflabs.org @firefox @internet_explorer_6 @internet_explorer_7 @internet_explorer_8 @internet_explorer_9 @internet_explorer_10 @login @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @internet_explorer_6 @internet_explorer_7 @internet_explorer_8 @internet_explorer_9 @internet_explorer_10 @login @phantomjs Feature: Preferences Scenario: Preferences Appearance diff --git a/tests/browser/features/view_history.feature b/tests/browser/features/view_history.feature index 7b42639afc..ba61ebda8a 100644 --- a/tests/browser/features/view_history.feature +++ b/tests/browser/features/view_history.feature @@ -1,4 +1,4 @@ -@chrome @clean @en.wikipedia.beta.wmflabs.org @firefox @phantomjs @test2.wikipedia.org +@chrome @clean @firefox @phantomjs Feature: View History Scenario: Edit page and view history @@ -8,4 +8,4 @@ Feature: View History And I save the edit And the edited page content should contain "Edited and a random string" And I click View History - Then I should see a link to a previous version of the page \ No newline at end of file + Then I should see a link to a previous version of the page diff --git a/tests/frontend/Gruntfile.js b/tests/frontend/Gruntfile.js index 59c18a8a1b..8630f5df7b 100644 --- a/tests/frontend/Gruntfile.js +++ b/tests/frontend/Gruntfile.js @@ -44,7 +44,7 @@ module.exports = function ( grunt ) { jsonlint: { all: [ '.jscsrc', - '{languages,languages,maintenance,resources}/**/*.json', + '{languages,maintenance,resources}/**/*.json', 'tests/frontend/package.json' ] }, diff --git a/tests/parser/preprocess/All_system_messages.expected b/tests/parser/preprocess/All_system_messages.expected index 078d8f0d01..2ee805e02b 100644 --- a/tests/parser/preprocess/All_system_messages.expected +++ b/tests/parser/preprocess/All_system_messages.expected @@ -2207,13 +2207,6 @@ Log in </td><td> </td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Loginend&action=edit loginend]<br> -[[MediaWiki_talk:Loginend|Talk]] -</td><td> -&amp;nbsp; -</td><td> - -</td></tr><tr><td> [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Loginerror&action=edit loginerror]<br> [[MediaWiki_talk:Loginerror|Talk]] </td><td> diff --git a/tests/parser/preprocess/All_system_messages.txt b/tests/parser/preprocess/All_system_messages.txt index 3c30da94eb..4a30f56f99 100644 --- a/tests/parser/preprocess/All_system_messages.txt +++ b/tests/parser/preprocess/All_system_messages.txt @@ -2207,13 +2207,6 @@ Log in {{int:Login}} -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Loginend&action=edit loginend]
    -[[MediaWiki_talk:Loginend|Talk]] - -&nbsp; - -{{int:Loginend}} - [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Loginerror&action=edit loginerror]
    [[MediaWiki_talk:Loginerror|Talk]] diff --git a/tests/parserTests.php b/tests/parserTests.php index 9965c4382c..5d21319bac 100644 --- a/tests/parserTests.php +++ b/tests/parserTests.php @@ -70,10 +70,6 @@ if ( $wgDBtype == 'sqlite' ) { } } -# There is a convention that the parser should never -# refer to $wgTitle directly, but instead use the title -# passed to it. -$wgTitle = Title::newFromText( 'Parser test script do not use' ); $tester = new ParserTest( $options ); if ( isset( $options['file'] ) ) { diff --git a/tests/phpunit/data/cssmin/circle.svg b/tests/phpunit/data/cssmin/circle.svg new file mode 100644 index 0000000000..6b7d1afd87 --- /dev/null +++ b/tests/phpunit/data/cssmin/circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/tests/phpunit/includes/GitInfoTest.php b/tests/phpunit/includes/GitInfoTest.php index e22f50507e..c3539d0e7d 100644 --- a/tests/phpunit/includes/GitInfoTest.php +++ b/tests/phpunit/includes/GitInfoTest.php @@ -10,7 +10,7 @@ class GitInfoTest extends MediaWikiTestCase { } public function testValidJsonData() { - $dir = $GLOBALS['IP'] . '/testValidJsonData'; + $dir = $GLOBALS['IP'] . DIRECTORY_SEPARATOR . 'testValidJsonData'; $fixture = new GitInfo( $dir ); $this->assertTrue( $fixture->cacheIsComplete() ); diff --git a/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php b/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php new file mode 100644 index 0000000000..54e1f896e7 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php @@ -0,0 +1,67 @@ +assertEquals( $expected, wfAppendQuery( $url, $query ), $message ); + } + + public static function provideAppendQuery() { + return array( + array( + 'http://www.example.org/index.php', + '', + 'http://www.example.org/index.php', + 'No query' + ), + array( + 'http://www.example.org/index.php', + array( 'foo' => 'bar' ), + 'http://www.example.org/index.php?foo=bar', + 'Set query array' + ), + array( + 'http://www.example.org/index.php?foz=baz', + 'foo=bar', + 'http://www.example.org/index.php?foz=baz&foo=bar', + 'Set query string' + ), + array( + 'http://www.example.org/index.php?foo=bar', + '', + 'http://www.example.org/index.php?foo=bar', + 'Empty string with query' + ), + array( + 'http://www.example.org/index.php?foo=bar', + array( 'baz' => 'quux' ), + 'http://www.example.org/index.php?foo=bar&baz=quux', + 'Add query array' + ), + array( + 'http://www.example.org/index.php?foo=bar', + 'baz=quux', + 'http://www.example.org/index.php?foo=bar&baz=quux', + 'Add query string' + ), + array( + 'http://www.example.org/index.php?foo=bar', + array( 'baz' => 'quux', 'foo' => 'baz' ), + 'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz', + 'Modify query array' + ), + array( + 'http://www.example.org/index.php?foo=bar', + 'baz=quux&foo=baz', + 'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz', + 'Modify query string' + ) + ); + } +} diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php index 59ffb90608..fbd2c31a30 100644 --- a/tests/phpunit/includes/HttpTest.php +++ b/tests/phpunit/includes/HttpTest.php @@ -179,6 +179,18 @@ class HttpTest extends MediaWikiTestCase { * Constant values are from PHP 5.3.28 using cURL 7.24.0 * @see http://php.net/manual/en/curl.constants.php * + * All constant values are present so that developers don’t need to remember + * to add them if added at a later date. The commented out constants were + * not found anywhere in the MediaWiki core code. + * + * Commented out constants that were not available in: + * HipHop VM 3.3.0 (rel) + * Compiler: heads/master-0-g08810d920dfff59e0774cf2d651f92f13a637175 + * Repo schema: 3214fc2c684a4520485f715ee45f33f2182324b1 + * Extension API: 20140829 + * + * Commented out constants that were removed in PHP 5.6.0 + * * @covers CurlHttpRequest::execute */ public function provideCurlConstants() { @@ -189,11 +201,11 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLAUTH_DIGEST' ), array( 'CURLAUTH_GSSNEGOTIATE' ), array( 'CURLAUTH_NTLM' ), - array( 'CURLCLOSEPOLICY_CALLBACK' ), - array( 'CURLCLOSEPOLICY_LEAST_RECENTLY_USED' ), - array( 'CURLCLOSEPOLICY_LEAST_TRAFFIC' ), - array( 'CURLCLOSEPOLICY_OLDEST' ), - array( 'CURLCLOSEPOLICY_SLOWEST' ), + // array( 'CURLCLOSEPOLICY_CALLBACK' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_LEAST_RECENTLY_USED' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_LEAST_TRAFFIC' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_OLDEST' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_SLOWEST' ), // removed in PHP 5.6.0 array( 'CURLE_ABORTED_BY_CALLBACK' ), array( 'CURLE_BAD_CALLING_ORDER' ), array( 'CURLE_BAD_CONTENT_ENCODING' ), @@ -245,7 +257,7 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLE_RECV_ERROR' ), array( 'CURLE_SEND_ERROR' ), array( 'CURLE_SHARE_IN_USE' ), - array( 'CURLE_SSH' ), + // array( 'CURLE_SSH' ), // not present in HHVM 3.3.0-dev array( 'CURLE_SSL_CACERT' ), array( 'CURLE_SSL_CERTPROBLEM' ), array( 'CURLE_SSL_CIPHER' ), @@ -263,14 +275,14 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLFTPAUTH_DEFAULT' ), array( 'CURLFTPAUTH_SSL' ), array( 'CURLFTPAUTH_TLS' ), - array( 'CURLFTPMETHOD_MULTICWD' ), - array( 'CURLFTPMETHOD_NOCWD' ), - array( 'CURLFTPMETHOD_SINGLECWD' ), + // array( 'CURLFTPMETHOD_MULTICWD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLFTPMETHOD_NOCWD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLFTPMETHOD_SINGLECWD' ), // not present in HHVM 3.3.0-dev array( 'CURLFTPSSL_ALL' ), array( 'CURLFTPSSL_CONTROL' ), array( 'CURLFTPSSL_NONE' ), array( 'CURLFTPSSL_TRY' ), - array( 'CURLINFO_CERTINFO' ), + // array( 'CURLINFO_CERTINFO' ), // not present in HHVM 3.3.0-dev array( 'CURLINFO_CONNECT_TIME' ), array( 'CURLINFO_CONTENT_LENGTH_DOWNLOAD' ), array( 'CURLINFO_CONTENT_LENGTH_UPLOAD' ), @@ -285,7 +297,7 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLINFO_PRIVATE' ), array( 'CURLINFO_REDIRECT_COUNT' ), array( 'CURLINFO_REDIRECT_TIME' ), - array( 'CURLINFO_REDIRECT_URL' ), + // array( 'CURLINFO_REDIRECT_URL' ), // not present in HHVM 3.3.0-dev array( 'CURLINFO_REQUEST_SIZE' ), array( 'CURLINFO_SIZE_DOWNLOAD' ), array( 'CURLINFO_SIZE_UPLOAD' ), @@ -306,8 +318,8 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLOPT_BUFFERSIZE' ), array( 'CURLOPT_CAINFO' ), array( 'CURLOPT_CAPATH' ), - array( 'CURLOPT_CERTINFO' ), - array( 'CURLOPT_CLOSEPOLICY' ), + // array( 'CURLOPT_CERTINFO' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_CLOSEPOLICY' ), // removed in PHP 5.6.0 array( 'CURLOPT_CONNECTTIMEOUT' ), array( 'CURLOPT_CONNECTTIMEOUT_MS' ), array( 'CURLOPT_COOKIE' ), @@ -331,8 +343,8 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLOPT_FTPPORT' ), array( 'CURLOPT_FTPSSLAUTH' ), array( 'CURLOPT_FTP_CREATE_MISSING_DIRS' ), - array( 'CURLOPT_FTP_FILEMETHOD' ), - array( 'CURLOPT_FTP_SKIP_PASV_IP' ), + // array( 'CURLOPT_FTP_FILEMETHOD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_FTP_SKIP_PASV_IP' ), // not present in HHVM 3.3.0-dev array( 'CURLOPT_FTP_SSL' ), array( 'CURLOPT_FTP_USE_EPRT' ), array( 'CURLOPT_FTP_USE_EPSV' ), @@ -348,14 +360,14 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLOPT_INFILESIZE' ), array( 'CURLOPT_INTERFACE' ), array( 'CURLOPT_IPRESOLVE' ), - array( 'CURLOPT_KEYPASSWD' ), + // array( 'CURLOPT_KEYPASSWD' ), // not present in HHVM 3.3.0-dev array( 'CURLOPT_KRB4LEVEL' ), array( 'CURLOPT_LOW_SPEED_LIMIT' ), array( 'CURLOPT_LOW_SPEED_TIME' ), array( 'CURLOPT_MAXCONNECTS' ), array( 'CURLOPT_MAXREDIRS' ), - array( 'CURLOPT_MAX_RECV_SPEED_LARGE' ), - array( 'CURLOPT_MAX_SEND_SPEED_LARGE' ), + // array( 'CURLOPT_MAX_RECV_SPEED_LARGE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_MAX_SEND_SPEED_LARGE' ), // not present in HHVM 3.3.0-dev array( 'CURLOPT_NETRC' ), array( 'CURLOPT_NOBODY' ), array( 'CURLOPT_NOPROGRESS' ), @@ -367,7 +379,7 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLOPT_POSTREDIR' ), array( 'CURLOPT_PRIVATE' ), array( 'CURLOPT_PROGRESSFUNCTION' ), - array( 'CURLOPT_PROTOCOLS' ), + // array( 'CURLOPT_PROTOCOLS' ), // not present in HHVM 3.3.0-dev array( 'CURLOPT_PROXY' ), array( 'CURLOPT_PROXYAUTH' ), array( 'CURLOPT_PROXYPORT' ), @@ -379,14 +391,14 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLOPT_RANGE' ), array( 'CURLOPT_READDATA' ), array( 'CURLOPT_READFUNCTION' ), - array( 'CURLOPT_REDIR_PROTOCOLS' ), + // array( 'CURLOPT_REDIR_PROTOCOLS' ), // not present in HHVM 3.3.0-dev array( 'CURLOPT_REFERER' ), array( 'CURLOPT_RESUME_FROM' ), array( 'CURLOPT_RETURNTRANSFER' ), - array( 'CURLOPT_SSH_AUTH_TYPES' ), - array( 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5' ), - array( 'CURLOPT_SSH_PRIVATE_KEYFILE' ), - array( 'CURLOPT_SSH_PUBLIC_KEYFILE' ), + // array( 'CURLOPT_SSH_AUTH_TYPES' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_SSH_PRIVATE_KEYFILE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_SSH_PUBLIC_KEYFILE' ), // not present in HHVM 3.3.0-dev array( 'CURLOPT_SSLCERT' ), array( 'CURLOPT_SSLCERTPASSWD' ), array( 'CURLOPT_SSLCERTTYPE' ), @@ -414,28 +426,28 @@ class HttpTest extends MediaWikiTestCase { array( 'CURLOPT_VERBOSE' ), array( 'CURLOPT_WRITEFUNCTION' ), array( 'CURLOPT_WRITEHEADER' ), - array( 'CURLPROTO_ALL' ), - array( 'CURLPROTO_DICT' ), - array( 'CURLPROTO_FILE' ), - array( 'CURLPROTO_FTP' ), - array( 'CURLPROTO_FTPS' ), - array( 'CURLPROTO_HTTP' ), - array( 'CURLPROTO_HTTPS' ), - array( 'CURLPROTO_LDAP' ), - array( 'CURLPROTO_LDAPS' ), - array( 'CURLPROTO_SCP' ), - array( 'CURLPROTO_SFTP' ), - array( 'CURLPROTO_TELNET' ), - array( 'CURLPROTO_TFTP' ), + // array( 'CURLPROTO_ALL' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_DICT' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_FILE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_FTP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_FTPS' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_HTTP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_HTTPS' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_LDAP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_LDAPS' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_SCP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_SFTP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_TELNET' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_TFTP' ), // not present in HHVM 3.3.0-dev array( 'CURLPROXY_HTTP' ), - array( 'CURLPROXY_SOCKS4' ), + // array( 'CURLPROXY_SOCKS4' ), // not present in HHVM 3.3.0-dev array( 'CURLPROXY_SOCKS5' ), - array( 'CURLSSH_AUTH_DEFAULT' ), - array( 'CURLSSH_AUTH_HOST' ), - array( 'CURLSSH_AUTH_KEYBOARD' ), - array( 'CURLSSH_AUTH_NONE' ), - array( 'CURLSSH_AUTH_PASSWORD' ), - array( 'CURLSSH_AUTH_PUBLICKEY' ), + // array( 'CURLSSH_AUTH_DEFAULT' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_HOST' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_KEYBOARD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_NONE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_PASSWORD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_PUBLICKEY' ), // not present in HHVM 3.3.0-dev array( 'CURLVERSION_NOW' ), array( 'CURL_HTTP_VERSION_1_0' ), array( 'CURL_HTTP_VERSION_1_1' ), @@ -457,8 +469,8 @@ class HttpTest extends MediaWikiTestCase { } /** - * Added this test based on an issue experienced with hhvm where it did - * not define a cURL constant. + * Added this test based on an issue experienced with HHVM 3.3.0-dev + * where it did not define a cURL constant. * * @bug 70570 * @dataProvider provideCurlConstants diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 47560e613e..4c5424c5ec 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -277,6 +277,55 @@ class MessageTest extends MediaWikiLangTestCase { ); } + public function messagePlaintextParamsProvider() { + return array( + array( + 'one $2
    foo
    [[Bar]] {{Baz}} <', + 'plain', + ), + + array( + // expect + 'one $2
    foo
    [[Bar]] {{Baz}} <', + // format + 'text', + ), + array( + 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;', + 'escaped', + ), + + array( + 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;', + 'parse', + ), + + array( + "

    one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;\n

    ", + 'parseAsBlock', + ), + ); + } + + /** + * @dataProvider messagePlaintextParamsProvider + * @covers Message::plaintextParams + */ + public function testMessagePlaintextParams( $expect, $format ) { + $lang = Language::factory( 'en' ); + + $msg = new RawMessage( '$1 $2' ); + $params = array( + 'one $2', + '
    foo
    [[Bar]] {{Baz}} <', + ); + $this->assertEquals( + $expect, + $msg->inLanguage( $lang )->plaintextParams( $params )->$format(), + "Fail formatting for $format" + ); + } + /** * @covers Message::inContentLanguage */ diff --git a/tests/phpunit/includes/PasswordTest.php b/tests/phpunit/includes/PasswordTest.php index ceb794b592..5ad8aca676 100644 --- a/tests/phpunit/includes/PasswordTest.php +++ b/tests/phpunit/includes/PasswordTest.php @@ -30,4 +30,10 @@ class PasswordTest extends MediaWikiTestCase { $this->assertFalse( $invalid1->equals( $invalid2 ) ); } + + public function testInvalidPlaintext() { + $invalid = User::getPasswordFactory()->newFromPlaintext( null ); + + $this->assertInstanceOf( 'InvalidPassword', $invalid ); + } } diff --git a/tests/phpunit/includes/PrefixSearchTest.php b/tests/phpunit/includes/PrefixSearchTest.php new file mode 100644 index 0000000000..5390dba63d --- /dev/null +++ b/tests/phpunit/includes/PrefixSearchTest.php @@ -0,0 +1,210 @@ +setMwGlobals( 'wgSpecialPages', array() ); + } + + protected function searchProvision( Array $results = null ) { + if ( $results === null ) { + $this->setMwGlobals( 'wgHooks', array() ); + } else { + $this->setMwGlobals( 'wgHooks', array( + 'PrefixSearchBackend' => array( + function ( $namespaces, $search, $limit, &$srchres ) use ( $results ) { + $srchres = $results; + return false; + } + ), + ) ); + } + } + + public function addDBData() { + $this->insertPage( 'Sandbox' ); + $this->insertPage( 'Bar' ); + $this->insertPage( 'Example' ); + $this->insertPage( 'Example Bar' ); + $this->insertPage( 'Example Foo' ); + $this->insertPage( 'Example Foo/Bar' ); + $this->insertPage( 'Example/Baz' ); + + $this->insertPage( 'Talk:Sandbox' ); + $this->insertPage( 'Talk:Example' ); + + $this->insertPage( 'User:Example' ); + } + + public static function provideSearch() { + return array( + array( array( + 'Empty string', + 'query' => '', + 'results' => array(), + ) ), + array( array( + 'Main namespace with title prefix', + 'query' => 'Ex', + 'results' => array( + 'Example', + 'Example/Baz', + 'Example Bar', + ), + ) ), + array( array( + 'Talk namespace prefix', + 'query' => 'Talk:', + 'results' => array( + 'Talk:Example', + 'Talk:Sandbox', + ), + ) ), + array( array( + 'User namespace prefix', + 'query' => 'User:', + 'results' => array( + 'User:Example', + ), + ) ), + array( array( + 'Special namespace prefix', + 'query' => 'Special:', + 'results' => array( + 'Special:ActiveUsers', + 'Special:AllMessages', + 'Special:AllMyFiles', + ), + ) ), + array( array( + 'Special namespace with prefix', + 'query' => 'Special:Un', + 'results' => array( + 'Special:Unblock', + 'Special:UncategorizedCategories', + 'Special:UncategorizedFiles', + ), + ) ), + array( array( + 'Special page name', + 'query' => 'Special:EditWatchlist', + 'results' => array( + 'Special:EditWatchlist', + ), + ) ), + array( array( + 'Special page subpages', + 'query' => 'Special:EditWatchlist/', + 'results' => array( + 'Special:EditWatchlist/clear', + 'Special:EditWatchlist/raw', + ), + ) ), + array( array( + 'Special page subpages with prefix', + 'query' => 'Special:EditWatchlist/cl', + 'results' => array( + 'Special:EditWatchlist/clear', + ), + ) ), + ); + } + + /** + * @dataProvider provideSearch + * @covers PrefixSearch::search + * @covers PrefixSearch::searchBackend + */ + public function testSearch( Array $case ) { + $this->searchProvision( null ); + $searcher = new StringPrefixSearch; + $results = $searcher->search( $case['query'], 3 ); + $this->assertEquals( + $case['results'], + $results, + $case[0] + ); + } + + public static function provideSearchBackend() { + return array( + array( array( + 'Simple case', + 'provision' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + 'query' => 'Bar', + 'results' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + ) ), + array( array( + 'Exact match not on top (bug 70958)', + 'provision' => array( + 'Barcelona', + 'Bar', + 'Barbara', + ), + 'query' => 'Bar', + 'results' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + ) ), + array( array( + 'Exact match missing (bug 70958)', + 'provision' => array( + 'Barcelona', + 'Barbara', + 'Bart', + ), + 'query' => 'Bar', + 'results' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + ) ), + array( array( + 'Exact match missing and not existing', + 'provision' => array( + 'Exile', + 'Exist', + 'External', + ), + 'query' => 'Ex', + 'results' => array( + 'Exile', + 'Exist', + 'External', + ), + ) ), + ); + } + + /** + * @dataProvider provideSearchBackend + * @covers PrefixSearch::searchBackend + */ + public function testSearchBackend( Array $case ) { + $this->searchProvision( $case['provision'] ); + $searcher = new StringPrefixSearch; + $results = $searcher->search( $case['query'], 3 ); + $this->assertEquals( + $case['results'], + $results, + $case[0] + ); + } +} diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index 50c1e50980..f960f48455 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -6,12 +6,6 @@ */ class SanitizerTest extends MediaWikiTestCase { - protected function setUp() { - parent::setUp(); - - AutoLoader::loadClass( 'Sanitizer' ); - } - /** * @covers Sanitizer::decodeCharReferences */ diff --git a/tests/phpunit/includes/actions/ActionTest.php b/tests/phpunit/includes/actions/ActionTest.php index cc6fb11a6f..6681c7a56e 100644 --- a/tests/phpunit/includes/actions/ActionTest.php +++ b/tests/phpunit/includes/actions/ActionTest.php @@ -117,6 +117,15 @@ class ActionTest extends MediaWikiTestCase { $this->assertEquals( 'revisiondelete', $actionName ); } + public function testGetActionName_whenCanNotUseWikiPage_defaultsToView() { + $request = new FauxRequest( array( 'action' => 'edit' ) ); + $context = new DerivativeContext( RequestContext::getMain() ); + $context->setRequest( $request ); + $actionName = Action::getActionName( $context ); + + $this->assertEquals( 'view', $actionName ); + } + /** * @dataProvider actionProvider * @param string $requestedAction diff --git a/tests/phpunit/includes/changes/EnhancedChangesListTest.php b/tests/phpunit/includes/changes/EnhancedChangesListTest.php index 40a11d2d38..7a826809ff 100644 --- a/tests/phpunit/includes/changes/EnhancedChangesListTest.php +++ b/tests/phpunit/includes/changes/EnhancedChangesListTest.php @@ -31,7 +31,7 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { 'mediawiki.special.changeslist', $styleModules, 'has mediawiki.special.changeslist' - ); + ); $this->assertContains( 'mediawiki.special.changeslist.enhanced', @@ -75,10 +75,10 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { $this->assertEquals( '', $html ); } - /** - * @todo more tests for actual formatting, this is more of a smoke test - */ - public function testEndRecentChangesList() { + /** + * @todo more tests for actual formatting, this is more of a smoke test + */ + public function testEndRecentChangesList() { $enhancedChangesList = $this->newEnhancedChangesList(); $enhancedChangesList->beginRecentChangesList(); @@ -92,7 +92,7 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { preg_match_all( '/td class="mw-enhanced-rc-nested"/', $html, $matches ); $this->assertCount( 2, $matches[0] ); - } + } /** * @return EnhancedChangesList diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php index 3ad3bfbd66..06973b092c 100644 --- a/tests/phpunit/includes/config/HashConfigTest.php +++ b/tests/phpunit/includes/config/HashConfigTest.php @@ -60,4 +60,4 @@ class HashConfigTest extends MediaWikiTestCase { $conf->set( 'one', '3' ); $this->assertEquals( '3', $conf->get( 'one' ) ); } -} \ No newline at end of file +} diff --git a/tests/phpunit/includes/content/ContentHandlerTest.php b/tests/phpunit/includes/content/ContentHandlerTest.php index f7449734c4..1a90d6e201 100644 --- a/tests/phpunit/includes/content/ContentHandlerTest.php +++ b/tests/phpunit/includes/content/ContentHandlerTest.php @@ -317,6 +317,8 @@ class ContentHandlerTest extends MediaWikiTestCase { * page. */ public function testGetAutosummary() { + $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) ); + $content = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT ); $title = Title::newFromText( 'Help:Test' ); // Create a new content object with no content diff --git a/tests/phpunit/includes/installer/DatabaseUpdaterTest.php b/tests/phpunit/includes/installer/DatabaseUpdaterTest.php new file mode 100644 index 0000000000..abff3e6820 --- /dev/null +++ b/tests/phpunit/includes/installer/DatabaseUpdaterTest.php @@ -0,0 +1,279 @@ +setAppliedUpdates( "test", array() ); + $expected = "updatelist-test-" . time() . "0"; + $actual = $db->lastInsertData['ul_key']; + $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) ); + $dbu->setAppliedUpdates( "test", array() ); + $expected = "updatelist-test-" . time() . "1"; + $actual = $db->lastInsertData['ul_key']; + $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) ); + } +} + +class FakeDatabase extends DatabaseBase { + public $lastInsertTable; + public $lastInsertData; + + function __construct() { + } + + function clearFlag( $arg ) { + } + + function setFlag( $arg ) { + } + + public function insert( $table, $a, $fname = __METHOD__, $options = array() ) { + $this->lastInsertTable = $table; + $this->lastInsertData = $a; + } + + /** + * Get the type of the DBMS, as it appears in $wgDBtype. + * + * @return string + */ + function getType() { + // TODO: Implement getType() method. + } + + /** + * Open a connection to the database. Usually aborts on failure + * + * @param string $server Database server host + * @param string $user Database user name + * @param string $password Database user password + * @param string $dbName Database name + * @return bool + * @throws DBConnectionError + */ + function open( $server, $user, $password, $dbName ) { + // TODO: Implement open() method. + } + + /** + * Fetch the next row from the given result object, in object form. + * Fields can be retrieved with $row->fieldname, with fields acting like + * member variables. + * If no more rows are available, false is returned. + * + * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc. + * @return stdClass|bool + * @throws DBUnexpectedError Thrown if the database returns an error + */ + function fetchObject( $res ) { + // TODO: Implement fetchObject() method. + } + + /** + * Fetch the next row from the given result object, in associative array + * form. Fields are retrieved with $row['fieldname']. + * If no more rows are available, false is returned. + * + * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc. + * @return array|bool + * @throws DBUnexpectedError Thrown if the database returns an error + */ + function fetchRow( $res ) { + // TODO: Implement fetchRow() method. + } + + /** + * Get the number of rows in a result object + * + * @param mixed $res A SQL result + * @return int + */ + function numRows( $res ) { + // TODO: Implement numRows() method. + } + + /** + * Get the number of fields in a result object + * @see http://www.php.net/mysql_num_fields + * + * @param mixed $res A SQL result + * @return int + */ + function numFields( $res ) { + // TODO: Implement numFields() method. + } + + /** + * Get a field name in a result object + * @see http://www.php.net/mysql_field_name + * + * @param mixed $res A SQL result + * @param int $n + * @return string + */ + function fieldName( $res, $n ) { + // TODO: Implement fieldName() method. + } + + /** + * Get the inserted value of an auto-increment row + * + * The value inserted should be fetched from nextSequenceValue() + * + * Example: + * $id = $dbw->nextSequenceValue( 'page_page_id_seq' ); + * $dbw->insert( 'page', array( 'page_id' => $id ) ); + * $id = $dbw->insertId(); + * + * @return int + */ + function insertId() { + // TODO: Implement insertId() method. + } + + /** + * Change the position of the cursor in a result object + * @see http://www.php.net/mysql_data_seek + * + * @param mixed $res A SQL result + * @param int $row + */ + function dataSeek( $res, $row ) { + // TODO: Implement dataSeek() method. + } + + /** + * Get the last error number + * @see http://www.php.net/mysql_errno + * + * @return int + */ + function lastErrno() { + // TODO: Implement lastErrno() method. + } + + /** + * Get a description of the last error + * @see http://www.php.net/mysql_error + * + * @return string + */ + function lastError() { + // TODO: Implement lastError() method. + } + + /** + * mysql_fetch_field() wrapper + * Returns false if the field doesn't exist + * + * @param string $table Table name + * @param string $field Field name + * + * @return Field + */ + function fieldInfo( $table, $field ) { + // TODO: Implement fieldInfo() method. + } + + /** + * Get information about an index into an object + * @param string $table Table name + * @param string $index Index name + * @param string $fname Calling function name + * @return mixed Database-specific index description class or false if the index does not exist + */ + function indexInfo( $table, $index, $fname = __METHOD__ ) { + // TODO: Implement indexInfo() method. + } + + /** + * Get the number of rows affected by the last write query + * @see http://www.php.net/mysql_affected_rows + * + * @return int + */ + function affectedRows() { + // TODO: Implement affectedRows() method. + } + + /** + * Wrapper for addslashes() + * + * @param string $s String to be slashed. + * @return string Slashed string. + */ + function strencode( $s ) { + // TODO: Implement strencode() method. + } + + /** + * Returns a wikitext link to the DB's website, e.g., + * return "[http://www.mysql.com/ MySQL]"; + * Should at least contain plain text, if for some reason + * your database has no website. + * + * @return string Wikitext of a link to the server software's web site + */ + function getSoftwareLink() { + // TODO: Implement getSoftwareLink() method. + } + + /** + * A string describing the current software version, like from + * mysql_get_server_info(). + * + * @return string Version information from the database server. + */ + function getServerVersion() { + // TODO: Implement getServerVersion() method. + } + + /** + * Closes underlying database connection + * @since 1.20 + * @return bool Whether connection was closed successfully + */ + protected function closeConnection() { + // TODO: Implement closeConnection() method. + } + + /** + * The DBMS-dependent part of query() + * + * @param string $sql SQL query. + * @return ResultWrapper|bool Result object to feed to fetchObject, + * fetchRow, ...; or false on failure + */ + protected function doQuery( $sql ) { + // TODO: Implement doQuery() method. + } +} + +class FakeDatabaseUpdater extends DatabaseUpdater { + function __construct( $db ) { + $this->db = $db; + self::$updateCounter = 0; + } + + /** + * Get an array of updates to perform on the database. Should return a + * multi-dimensional array. The main key is the MediaWiki version (1.12, + * 1.13...) with the values being arrays of updates, identical to how + * updaters.inc did it (for now) + * + * @return array + */ + protected function getCoreUpdateList() { + return array(); + } + + public function canUseNewUpdatelog() { + return true; + } + + public function setAppliedUpdates( $version, $updates = array() ) { + parent::setAppliedUpdates( $version, $updates ); + } +} diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php index bf58ee3521..af68ab03ad 100644 --- a/tests/phpunit/includes/json/FormatJsonTest.php +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -123,6 +123,105 @@ class FormatJsonTest extends MediaWikiTestCase { ); } + public static function provideParse() { + return array( + array( null ), + array( true ), + array( false ), + array( 0 ), + array( 1 ), + array( 1.2 ), + array( '' ), + array( 'str' ), + array( array( 0, 1, 2 ) ), + array( array( 'a' => 'b' ) ), + array( array( 'a' => 'b' ) ), + array( array( 'a' => 'b', 'x' => array( 'c' => 'd' ) ) ), + ); + } + + /** + * Recursively convert arrays into stdClass + * @param array|string|bool|int|float|null $value + * @return stdClass|string|bool|int|float|null + */ + public static function toObject( $value ) { + return !is_array( $value ) ? $value : (object) array_map( __METHOD__, $value ); + } + + /** + * @dataProvider provideParse + * @param mixed $value + */ + public function testParse( $value ) { + $expected = self::toObject( $value ); + $json = FormatJson::encode( $expected, false, FormatJson::ALL_OK ); + $this->assertJson( $json ); + + $st = FormatJson::parse( $json ); + $this->assertType( 'Status', $st ); + $this->assertTrue( $st->isGood() ); + $this->assertEquals( $expected, $st->getValue() ); + + $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC ); + $this->assertType( 'Status', $st ); + $this->assertTrue( $st->isGood() ); + $this->assertEquals( $value, $st->getValue() ); + } + + public static function provideParseTryFixing() { + return array( + array( "[,]", '[]' ), + array( "[ , ]", '[]' ), + array( "[ , }", false ), + array( '[1],', false ), + array( "[1,]", '[1]' ), + array( "[1\n,]", '[1]' ), + array( "[1,\n]", '[1]' ), + array( "[1,]\n", '[1]' ), + array( "[1\n,\n]\n", '[1]' ), + array( '["a,",]', '["a,"]' ), + array( "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ), + array( '[[1,],[2,],[3,]]', false ), // I wish we could parse this, but would need quote parsing + array( '[1,,]', false ), + ); + } + + /** + * @dataProvider provideParseTryFixing + * @param string $value + * @param string|bool $expected + */ + public function testParseTryFixing( $value, $expected ) { + $st = FormatJson::parse( $value, FormatJson::TRY_FIXING ); + $this->assertType( 'Status', $st ); + if ( $expected === false ) { + $this->assertFalse( $st->isOK() ); + } else { + $this->assertFalse( $st->isGood() ); + $this->assertTrue( $st->isOK() ); + $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK ); + $this->assertEquals( $expected, $val ); + } + } + + public static function provideParseErrors() { + return array( + array( 'aaa' ), + array( '{"j": 1 ] }' ), + ); + } + + /** + * @dataProvider provideParseErrors + * @param mixed $value + */ + public function testParseErrors( $value ) { + $st = FormatJson::parse( $value ); + $this->assertType( 'Status', $st ); + $this->assertFalse( $st->isOK() ); + } + /** * Generate a set of test cases for a particular combination of encoder options. * diff --git a/tests/phpunit/includes/libs/CSSJanusTest.php b/tests/phpunit/includes/libs/CSSJanusTest.php deleted file mode 100644 index 8d0224cd20..0000000000 --- a/tests/phpunit/includes/libs/CSSJanusTest.php +++ /dev/null @@ -1,633 +0,0 @@ -assertEquals( $transformedA, $cssB, 'Test A-B transformation' ); - - $transformedB = CSSJanus::transform( $cssB ); - $this->assertEquals( $transformedB, $cssA, 'Test B-A transformation' ); - } else { - // If no B version is provided, it means - // the output should equal the input (modulo @noflip annotations). - $transformedA = CSSJanus::transform( $cssA ); - $this->assertEquals( $transformedA, $cssA, 'Nothing was flipped' ); - } - } - - /** - * @dataProvider provideTransformAdvancedCases - */ - public function testTransformAdvanced( $code, $expectedOutput, $options = array() ) { - $swapLtrRtlInURL = isset( $options['swapLtrRtlInURL'] ) ? - $options['swapLtrRtlInURL'] : false; - $swapLeftRightInURL = isset( $options['swapLeftRightInURL'] ) ? - $options['swapLeftRightInURL'] : false; - - $flipped = CSSJanus::transform( $code, $swapLtrRtlInURL, $swapLeftRightInURL ); - - $this->assertEquals( $expectedOutput, $flipped, - 'Test flipping, options: url-ltr-rtl=' . ( $swapLtrRtlInURL ? 'true' : 'false' ) - . ' url-left-right=' . ( $swapLeftRightInURL ? 'true' : 'false' ) - ); - } - - /** - * @dataProvider provideTransformBrokenCases - * @group Broken - */ - public function testTransformBroken( $code, $expectedOutput ) { - $flipped = CSSJanus::transform( $code ); - - $this->assertEquals( $expectedOutput, $flipped, 'Test flipping' ); - } - - /** - * These transform cases are tested *in both directions* - * No need to declare a principle twice in both directions here. - */ - public static function provideTransformCases() { - return array( - // Property keys - array( - '.foo { left: 0; }', - '.foo { right: 0; }' - ), - // Guard against partial keys - // (CSS currently doesn't have flippable properties - // that contain the direction as part of the key without - // dash separation) - array( - '.foo { alright: 0; }' - ), - array( - '.foo { balleft: 0; }' - ), - - // Dashed property keys - array( - '.foo { padding-left: 0; }', - '.foo { padding-right: 0; }' - ), - array( - '.foo { margin-left: 0; }', - '.foo { margin-right: 0; }' - ), - array( - '.foo { border-left: 0; }', - '.foo { border-right: 0; }' - ), - - // Double-dashed property keys - array( - '.foo { border-left-color: red; }', - '.foo { border-right-color: red; }' - ), - array( - // Includes unknown properties? - '.foo { x-left-y: 0; }', - '.foo { x-right-y: 0; }' - ), - - // Multi-value properties - array( - '.foo { padding: 0; }' - ), - array( - '.foo { padding: 0 1px; }' - ), - array( - '.foo { padding: 0 1px 2px; }' - ), - array( - '.foo { padding: 0 1px 2px 3px; }', - '.foo { padding: 0 3px 2px 1px; }' - ), - - // Shorthand / Four notation - array( - '.foo { padding: .25em 15px 0pt 0ex; }', - '.foo { padding: .25em 0ex 0pt 15px; }' - ), - array( - '.foo { margin: 1px -4px 3px 2px; }', - '.foo { margin: 1px 2px 3px -4px; }' - ), - array( - '.foo { padding: 0 15px .25em 0; }', - '.foo { padding: 0 0 .25em 15px; }' - ), - array( - '.foo { padding: 1px 4.1grad 3px 2%; }', - '.foo { padding: 1px 2% 3px 4.1grad; }' - ), - array( - '.foo { padding: 1px 2px 3px auto; }', - '.foo { padding: 1px auto 3px 2px; }' - ), - array( - '.foo { padding: 1px inherit 3px auto; }', - '.foo { padding: 1px auto 3px inherit; }' - ), - // border-radius assigns different meanings to the values - array( - '.foo { border-radius: .25em 15px 0pt 0ex; }', - '.foo { border-radius: 15px .25em 0ex 0pt; }' - ), - array( - '.foo { border-radius: 0px 0px 5px 5px; }', - ), - // Ensure the rule doesn't break other stuff - array( - '.foo { x-unknown: a b c d; }' - ), - array( - '.foo barpx 0 2% { opacity: 0; }' - ), - array( - '#settings td p strong' - ), - array( - // Color names - '.foo { border-color: red green blue white }', - '.foo { border-color: red white blue green }', - ), - array( - // Color name, hexdecimal, RGB & RGBA - '.foo { border-color: red #f00 rgb(255, 0, 0) rgba(255, 0, 0, 0.5) }', - '.foo { border-color: red rgba(255, 0, 0, 0.5) rgb(255, 0, 0) #f00 }', - ), - array( - // Color name, hexdecimal, HSL & HSLA - '.foo { border-color: red #f00 hsl(0, 100%, 50%) hsla(0, 100%, 50%, 0.5) }', - '.foo { border-color: red hsla(0, 100%, 50%, 0.5) hsl(0, 100%, 50%) #f00 }', - ), - array( - // Do not mangle 5 or more values - '.foo { -x-unknown: 1 2 3 4 5; }' - ), - array( - '.foo { -x-unknown: 1 2 3 4 5 6; }' - ), - - // Shorthand / Three notation - array( - '.foo { margin: 1em 0 .25em; }' - ), - array( - '.foo { margin:-1.5em 0 -.75em; }' - ), - - // Shorthand / Two notation - array( - '.foo { padding: 1px 2px; }' - ), - - // Shorthand / One notation - array( - '.foo { padding: 1px; }' - ), - - // text-shadow and box-shadow - array( - '.foo { box-shadow: -6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', - '.foo { box-shadow: 6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', - ), - array( - '.foo { box-shadow: inset -6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', - '.foo { box-shadow: inset 6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', - ), - array( - '.foo { text-shadow: orange 2px 0; }', - '.foo { text-shadow: orange -2px 0; }', - ), - array( - '.foo { text-shadow: 2px 0 orange; }', - '.foo { text-shadow: -2px 0 orange; }', - ), - array( - // Don't mangle zeroes - '.foo { text-shadow: orange 0 2px; }' - ), - array( - // Make sure floats are not considered zero - '.foo { box-shadow: inset .5em 0 0 white; }', - '.foo { box-shadow: inset -.5em 0 0 white; }', - ), - - // Direction - // Note: This differs from the Python implementation, - // see also CSSJanus::fixDirection for more info. - array( - '.foo { direction: ltr; }', - '.foo { direction: rtl; }' - ), - array( - '.foo { direction: rtl; }', - '.foo { direction: ltr; }' - ), - array( - 'input { direction: ltr; }', - 'input { direction: rtl; }' - ), - array( - 'input { direction: rtl; }', - 'input { direction: ltr; }' - ), - array( - 'body { direction: ltr; }', - 'body { direction: rtl; }' - ), - array( - '.foo, body, input { direction: ltr; }', - '.foo, body, input { direction: rtl; }' - ), - array( - 'body { padding: 10px; direction: ltr; }', - 'body { padding: 10px; direction: rtl; }' - ), - array( - 'body { direction: ltr } .myClass { direction: ltr }', - 'body { direction: rtl } .myClass { direction: rtl }' - ), - - // Left/right values - array( - '.foo { float: left; }', - '.foo { float: right; }' - ), - array( - '.foo { text-align: left; }', - '.foo { text-align: right; }' - ), - array( - '.foo { -x-unknown: left; }', - '.foo { -x-unknown: right; }' - ), - // Guard against selectors that look flippable - array( - '.column-left { width: 0; }' - ), - array( - 'a.left { width: 0; }' - ), - array( - 'a.leftification { width: 0; }' - ), - array( - 'a.ltr { width: 0; }' - ), - array( - #
    - '.a-ltr.png { width: 0; }' - ), - array( - # - 'foo-ltr[attr="x"] { width: 0; }' - ), - array( - 'div.left > span.right+span.left { width: 0; }' - ), - array( - '.thisclass .left .myclass { width: 0; }' - ), - array( - '.thisclass .left .myclass #myid { width: 0; }' - ), - - // Cursor values (east/west) - array( - '.foo { cursor: e-resize; }', - '.foo { cursor: w-resize; }' - ), - array( - '.foo { cursor: se-resize; }', - '.foo { cursor: sw-resize; }' - ), - array( - '.foo { cursor: ne-resize; }', - '.foo { cursor: nw-resize; }' - ), - - // Background - array( - '.foo { background-position: top left; }', - '.foo { background-position: top right; }' - ), - array( - '.foo { background: url(/foo/bar.png) top left; }', - '.foo { background: url(/foo/bar.png) top right; }' - ), - array( - '.foo { background: url(/foo/bar.png) top left no-repeat; }', - '.foo { background: url(/foo/bar.png) top right no-repeat; }' - ), - array( - '.foo { background: url(/foo/bar.png) no-repeat top left; }', - '.foo { background: url(/foo/bar.png) no-repeat top right; }' - ), - array( - '.foo { background: #fff url(/foo/bar.png) no-repeat top left; }', - '.foo { background: #fff url(/foo/bar.png) no-repeat top right; }' - ), - array( - '.foo { background-position: 100% 40%; }', - '.foo { background-position: 0% 40%; }' - ), - array( - '.foo { background-position: 23% 0; }', - '.foo { background-position: 77% 0; }' - ), - array( - '.foo { background-position: 23% auto; }', - '.foo { background-position: 77% auto; }' - ), - array( - '.foo { background-position-x: 23%; }', - '.foo { background-position-x: 77%; }' - ), - array( - '.foo { background-position-y: 23%; }', - '.foo { background-position-y: 23%; }' - ), - array( - '.foo { background:url(../foo.png) no-repeat 75% 50%; }', - '.foo { background:url(../foo.png) no-repeat 25% 50%; }' - ), - array( - '.foo { background: 10% 20% } .bar { background: 40% 30% }', - '.foo { background: 90% 20% } .bar { background: 60% 30% }' - ), - - // Multiple rules - array( - 'body { direction: rtl; float: right; } .foo { direction: ltr; float: right; }', - 'body { direction: ltr; float: left; } .foo { direction: rtl; float: left; }', - ), - - // Duplicate properties - array( - '.foo { float: left; float: right; float: left; }', - '.foo { float: right; float: left; float: right; }', - ), - - // Preserve comments - array( - '/* left /* right */left: 10px', - '/* left /* right */right: 10px' - ), - array( - '/*left*//*left*/left: 10px', - '/*left*//*left*/right: 10px' - ), - array( - '/* Going right is cool */ .foo { width: 0 }', - ), - array( - "/* padding-right 1 2 3 4 */\n#test { width: 0}\n/*right*/" - ), - array( - "/** Two line comment\n * left\n \*/\n#test {width: 0}" - ), - - // @noflip annotation - array( - // before selector (single) - '/* @noflip */ div { float: left; }' - ), - array( - // before selector (multiple) - '/* @noflip */ div, .notme { float: left; }' - ), - array( - // inside selector - 'div, /* @noflip */ .foo { float: left; }' - ), - array( - // after selector - 'div, .notme /* @noflip */ { float: left; }' - ), - array( - // before multiple rules - '/* @noflip */ div { float: left; } .foo { float: left; }', - '/* @noflip */ div { float: left; } .foo { float: right; }' - ), - array( - // support parentheses in selector - '/* @noflip */ .test:not(:first) { margin-right: -0.25em; margin-left: 0.25em; }', - '/* @noflip */ .test:not(:first) { margin-right: -0.25em; margin-left: 0.25em; }' - ), - array( - // after multiple rules - '.foo { float: left; } /* @noflip */ div { float: left; }', - '.foo { float: right; } /* @noflip */ div { float: left; }' - ), - array( - // before multiple properties - 'div { /* @noflip */ float: left; text-align: left; }', - 'div { /* @noflip */ float: left; text-align: right; }' - ), - array( - // after multiple properties - 'div { float: left; /* @noflip */ text-align: left; }', - 'div { float: right; /* @noflip */ text-align: left; }' - ), - array( - // before a *= attribute selector with multiple properties - '/* @noflip */ div.foo[bar*=baz] { float:left; clear: left; }' - ), - array( - // before a ^= attribute selector with multiple properties - '/* @noflip */ div.foo[bar^=baz] { float:left; clear: left; }' - ), - array( - // before a ~= attribute selector with multiple properties - '/* @noflip */ div.foo[bar~=baz] { float:left; clear: left; }' - ), - array( - // before a = attribute selector with multiple properties - '/* @noflip */ div.foo[bar=baz] { float:left; clear: left; }' - ), - array( - // before a quoted attribute selector with multiple properties - '/* @noflip */ div.foo[bar=\'baz{quux\'] { float:left; clear: left; }' - ), - - // Guard against css3 stuff - array( - 'background-image: -moz-linear-gradient(#326cc1, #234e8c);' - ), - array( - 'background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, from(#666666), to(#ffffff));' - ), - - // CSS syntax / white-space variations - // spaces, no spaces, tabs, new lines, omitting semi-colons - array( - ".foo { left: 0; }", - ".foo { right: 0; }" - ), - array( - ".foo{ left: 0; }", - ".foo{ right: 0; }" - ), - array( - ".foo{ left: 0 }", - ".foo{ right: 0 }" - ), - array( - ".foo{left:0 }", - ".foo{right:0 }" - ), - array( - ".foo{left:0}", - ".foo{right:0}" - ), - array( - ".foo { left : 0 ; }", - ".foo { right : 0 ; }" - ), - array( - ".foo\n { left : 0 ; }", - ".foo\n { right : 0 ; }" - ), - array( - ".foo\n { \nleft : 0 ; }", - ".foo\n { \nright : 0 ; }" - ), - array( - ".foo\n { \n left : 0 ; }", - ".foo\n { \n right : 0 ; }" - ), - array( - ".foo\n { \n left\n : 0; }", - ".foo\n { \n right\n : 0; }" - ), - array( - ".foo \n { \n left\n : 0; }", - ".foo \n { \n right\n : 0; }" - ), - array( - ".foo\n{\nleft\n:\n0;}", - ".foo\n{\nright\n:\n0;}" - ), - array( - ".foo\n.bar {\n\tleft: 0;\n}", - ".foo\n.bar {\n\tright: 0;\n}" - ), - array( - ".foo\t{\tleft\t:\t0;}", - ".foo\t{\tright\t:\t0;}" - ), - - // Guard against partial keys - array( - '.foo { leftxx: 0; }', - '.foo { leftxx: 0; }' - ), - array( - '.foo { rightxx: 0; }', - '.foo { rightxx: 0; }' - ), - ); - } - - /** - * These cases are tested in one way only (format: actual, expected, msg). - * If both ways can be tested, either put both versions in here or move - * it to provideTransformCases(). - */ - public static function provideTransformAdvancedCases() { - $bgPairs = array( - # [ - _ . ] <-> [ left right ltr rtl ] - 'foo.jpg' => 'foo.jpg', - 'left.jpg' => 'right.jpg', - 'ltr.jpg' => 'rtl.jpg', - - 'foo-left.png' => 'foo-right.png', - 'foo_left.png' => 'foo_right.png', - 'foo.left.png' => 'foo.right.png', - - 'foo-ltr.png' => 'foo-rtl.png', - 'foo_ltr.png' => 'foo_rtl.png', - 'foo.ltr.png' => 'foo.rtl.png', - - 'left-foo.png' => 'right-foo.png', - 'left_foo.png' => 'right_foo.png', - 'left.foo.png' => 'right.foo.png', - - 'ltr-foo.png' => 'rtl-foo.png', - 'ltr_foo.png' => 'rtl_foo.png', - 'ltr.foo.png' => 'rtl.foo.png', - - 'foo-ltr-left.gif' => 'foo-rtl-right.gif', - 'foo_ltr_left.gif' => 'foo_rtl_right.gif', - 'foo.ltr.left.gif' => 'foo.rtl.right.gif', - 'foo-ltr_left.gif' => 'foo-rtl_right.gif', - 'foo_ltr.left.gif' => 'foo_rtl.right.gif', - ); - $provider = array(); - foreach ( $bgPairs as $left => $right ) { - # By default '-rtl' and '-left' etc. are not touched, - # Only when the appropiate parameter is set. - $provider[] = array( - ".foo { background: url(images/$left); }", - ".foo { background: url(images/$left); }" - ); - $provider[] = array( - ".foo { background: url(images/$right); }", - ".foo { background: url(images/$right); }" - ); - $provider[] = array( - ".foo { background: url(images/$left); }", - ".foo { background: url(images/$right); }", - array( - 'swapLtrRtlInURL' => true, - 'swapLeftRightInURL' => true, - ) - ); - $provider[] = array( - ".foo { background: url(images/$right); }", - ".foo { background: url(images/$left); }", - array( - 'swapLtrRtlInURL' => true, - 'swapLeftRightInURL' => true, - ) - ); - } - - return $provider; - } - - /** - * Cases that are currently failing, but - * should be looked at in the future as enhancements and/or bug fix - */ - public static function provideTransformBrokenCases() { - return array( - // Guard against selectors that look flippable - array( - # - 'foo-left-x[attr="x"] { width: 0; }', - 'foo-left-x[attr="x"] { width: 0; }' - ), - array( - #
    - '.foo[data-left="x"] { width: 0; }', - '.foo[data-left="x"] { width: 0; }' - ), - ); - } -} diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index 43c5086950..6fa3acf81f 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -147,9 +147,12 @@ class CSSMinTest extends MediaWikiTestCase { // Full paths start with http://localhost/w/. // Timestamps in output are replaced with 'timestamp'. - // data: URIs for red.gif and green.gif + // data: URIs for red.gif, green.gif, circle.svg $red = 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs='; $green = 'data:image/gif;base64,R0lGODlhAQABAIAAAACAADAAACwAAAAAAQABAAACAkQBADs='; + $svg = 'data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A' + . '%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%228%22%20height%3D' + . '%228%22%3E%0A%3Ccircle%20cx%3D%224%22%20cy%3D%224%22%20r%3D%222%22%2F%3E%0A%3C%2Fsvg%3E%0A'; return array( array( @@ -233,6 +236,11 @@ class CSSMinTest extends MediaWikiTestCase { 'foo { /* @embed */ background: url(large.png); }', "foo { background: url(http://localhost/w/large.png?timestamp); }", ), + array( + 'SVG files are embedded without base64 encoding and unnecessary IE 6 and 7 fallback', + 'foo { /* @embed */ background: url(circle.svg); }', + "foo { background: url($svg); }", + ), array( 'Two regular files in one rule', 'foo { background: url(red.gif), url(green.gif); }', diff --git a/tests/phpunit/includes/mail/MailAddressTest.php b/tests/phpunit/includes/mail/MailAddressTest.php index 76566eb03e..18d2acdf38 100644 --- a/tests/phpunit/includes/mail/MailAddressTest.php +++ b/tests/phpunit/includes/mail/MailAddressTest.php @@ -14,6 +14,9 @@ class MailAddressTest extends MediaWikiTestCase { * @covers MailAddress::newFromUser */ public function testNewFromUser() { + if ( wfIsWindows() ) { + $this->markTestSkipped( 'This test only works on non-Windows platforms' ); + } $user = $this->getMock( 'User' ); $user->expects( $this->any() )->method( 'getName' )->will( $this->returnValue( 'UserName' ) ); $user->expects( $this->any() )->method( 'getEmail' )->will( $this->returnValue( 'foo@bar.baz' ) ); @@ -60,5 +63,4 @@ class MailAddressTest extends MediaWikiTestCase { $ma = new MailAddress( 'some@email.com', 'UserName', 'A real name' ); $this->assertEquals( $ma->toString(), (string)$ma ); } - -} \ No newline at end of file +} diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php index ab33d1c22c..0241aec4b5 100644 --- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php @@ -6,11 +6,6 @@ */ class SVGMetadataExtractorTest extends MediaWikiTestCase { - protected function setUp() { - parent::setUp(); - AutoLoader::loadClass( 'SVGMetadataExtractorTest' ); - } - /** * @dataProvider provideSvgFiles */ diff --git a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php index d56ecad1d3..cb122732de 100644 --- a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php +++ b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php @@ -22,6 +22,31 @@ */ class SpecialPageFactoryTest extends MediaWikiTestCase { + protected function tearDown() { + parent::tearDown(); + + SpecialPageFactory::resetList(); + } + + public function testResetList() { + SpecialPageFactory::resetList(); + $this->assertContains( 'Specialpages', SpecialPageFactory::getNames() ); + } + + public function testHookNotCalledTwice() { + $count = 0; + $this->mergeMwGlobalArrayValue( 'wgHooks', array( + 'SpecialPage_initList' => array( + function () use ( &$count ) { + $count++; + } + ) ) ); + SpecialPageFactory::resetList(); + SpecialPageFactory::getNames(); + SpecialPageFactory::getNames(); + $this->assertEquals( 1, $count ); + } + public function newSpecialAllPages() { return new SpecialAllPages(); } @@ -29,10 +54,10 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { public function specialPageProvider() { return array( 'class name' => array( 'SpecialAllPages', false ), - 'closure' => array( function() { + 'closure' => array( function () { return new SpecialAllPages(); }, false ), - 'function' => array( array( $this, 'newSpecialAllPages' ), false ), + 'function' => array( array( $this, 'newSpecialAllPages' ), false ), ); } @@ -42,7 +67,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetPage( $spec, $shouldReuseInstance ) { $this->mergeMwGlobalArrayValue( 'wgSpecialPages', array( 'testdummy' => $spec ) ); - SpecialPageFactory::resetList(); $page = SpecialPageFactory::getPage( 'testdummy' ); @@ -50,8 +74,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $page2 = SpecialPageFactory::getPage( 'testdummy' ); $this->assertEquals( $shouldReuseInstance, $page2 === $page, "Should re-use instance:" ); - - SpecialPageFactory::resetList(); } /** @@ -59,12 +81,11 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetNames() { $this->mergeMwGlobalArrayValue( 'wgSpecialPages', array( 'testdummy' => 'SpecialAllPages' ) ); - SpecialPageFactory::resetList(); + $names = SpecialPageFactory::getNames(); $this->assertInternalType( 'array', $names ); $this->assertContains( 'testdummy', $names ); - SpecialPageFactory::resetList(); } /** @@ -72,14 +93,11 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testResolveAlias() { $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); - SpecialPageFactory::resetList(); list( $name, $param ) = SpecialPageFactory::resolveAlias( 'Spezialseiten/Foo' ); $this->assertEquals( 'Specialpages', $name ); $this->assertEquals( 'Foo', $param ); - - SpecialPageFactory::resetList(); } /** @@ -87,13 +105,10 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetLocalNameFor() { $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); - SpecialPageFactory::resetList(); $name = SpecialPageFactory::getLocalNameFor( 'Specialpages', 'Foo' ); $this->assertEquals( 'Spezialseiten/Foo', $name ); - - SpecialPageFactory::resetList(); } /** @@ -101,14 +116,157 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetTitleForAlias() { $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); - SpecialPageFactory::resetList(); $title = SpecialPageFactory::getTitleForAlias( 'Specialpages/Foo' ); $this->assertEquals( 'Spezialseiten/Foo', $title->getText() ); $this->assertEquals( NS_SPECIAL, $title->getNamespace() ); + } + + /** + * @dataProvider provideTestConflictResolution + */ + public function testConflictResolution( + $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings + ) { + global $wgContLang; + $lang = clone $wgContLang; + $lang->mExtendedSpecialPageAliases = $aliasesList; + $this->setMwGlobals( 'wgContLang', $lang ); + $this->setMwGlobals( 'wgSpecialPages', + array_combine( array_keys( $aliasesList ), array_keys( $aliasesList ) ) + ); + SpecialPageFactory::resetList(); + + // Catch the warnings we expect to be raised + $warnings = array(); + $this->setMwGlobals( 'wgDevelopmentWarnings', true ); + set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) { + if ( preg_match( '/First alias \'[^\']*\' for .*/', $errstr ) || + preg_match( '/Did not find a usable alias for special page .*/', $errstr ) + ) { + $warnings[] = $errstr; + return true; + } + return false; + } ); + $reset = new ScopedCallback( 'restore_error_handler' ); + + list( $name, /*...*/ ) = SpecialPageFactory::resolveAlias( $alias ); + $this->assertEquals( $expectedName, $name, "$test: Alias to name" ); + $result = SpecialPageFactory::getLocalNameFor( $name ); + $this->assertEquals( $expectedAlias, $result, "$test: Alias to name to alias" ); + + $gotWarnings = count( $warnings ); + if ( $gotWarnings !== $expectWarnings ) { + $this->fail( "Expected $expectWarnings warning(s), but got $gotWarnings:\n" . + join( "\n", $warnings ) + ); + } + } + + /** + * @dataProvider provideTestConflictResolution + */ + public function testConflictResolutionReversed( + $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings + ) { + // Make sure order doesn't matter by reversing the list + $aliasesList = array_reverse( $aliasesList ); + return $this->testConflictResolution( + $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings + ); + } + + public function provideTestConflictResolution() { + return array( + array( + 'Canonical name wins', + array( 'Foo' => array( 'Foo', 'Bar' ), 'Baz' => array( 'Foo', 'BazPage', 'Baz2' ) ), + 'Foo', + 'Foo', + 'Foo', + 1, + ), + + array( + 'Doesn\'t redirect to a different special page\'s canonical name', + array( 'Foo' => array( 'Foo', 'Bar' ), 'Baz' => array( 'Foo', 'BazPage', 'Baz2' ) ), + 'Baz', + 'Baz', + 'BazPage', + 1, + ), + + array( + 'Canonical name wins even if not aliased', + array( 'Foo' => array( 'FooPage' ), 'Baz' => array( 'Foo', 'BazPage', 'Baz2' ) ), + 'Foo', + 'Foo', + 'FooPage', + 1, + ), + + array( + 'Doesn\'t redirect to a different special page\'s canonical name even if not aliased', + array( 'Foo' => array( 'FooPage' ), 'Baz' => array( 'Foo', 'BazPage', 'Baz2' ) ), + 'Baz', + 'Baz', + 'BazPage', + 1, + ), + + array( + 'First local name beats non-first', + array( 'First' => array( 'Foo' ), 'NonFirst' => array( 'Bar', 'Foo' ) ), + 'Foo', + 'First', + 'Foo', + 0, + ), + + array( + 'Doesn\'t redirect to a different special page\'s first alias', + array( + 'Foo' => array( 'Foo' ), + 'First' => array( 'Bar' ), + 'Baz' => array( 'Foo', 'Bar', 'BazPage', 'Baz2' ) + ), + 'Baz', + 'Baz', + 'BazPage', + 1, + ), + + array( + 'Doesn\'t redirect wrong even if all aliases conflict', + array( + 'Foo' => array( 'Foo' ), + 'First' => array( 'Bar' ), + 'Baz' => array( 'Foo', 'Bar' ) + ), + 'Baz', + 'Baz', + 'Baz', + 2, + ), + + ); + } + public function testGetAliasListRecursion() { + $called = false; + $this->mergeMwGlobalArrayValue( 'wgHooks', array( + 'SpecialPage_initList' => array( + function () use ( &$called ) { + SpecialPageFactory::getLocalNameFor( 'Specialpages' ); + $called = true; + } + ), + ) ); SpecialPageFactory::resetList(); + SpecialPageFactory::getLocalNameFor( 'Specialpages' ); + $this->assertTrue( $called, 'Recursive call succeeded' ); } } diff --git a/tests/phpunit/includes/specials/SpecialBooksourcesTest.php b/tests/phpunit/includes/specials/SpecialBooksourcesTest.php new file mode 100644 index 0000000000..d341ccf128 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialBooksourcesTest.php @@ -0,0 +1,36 @@ +assertSame( $isValid, SpecialBooksources::isValidISBN( $isbn ) ); + } +} diff --git a/tests/phpunit/includes/upload/UploadBaseTest.php b/tests/phpunit/includes/upload/UploadBaseTest.php index 4f176015df..f23b264f24 100644 --- a/tests/phpunit/includes/upload/UploadBaseTest.php +++ b/tests/phpunit/includes/upload/UploadBaseTest.php @@ -9,21 +9,17 @@ class UploadBaseTest extends MediaWikiTestCase { protected $upload; protected function setUp() { - global $wgHooks; parent::setUp(); $this->upload = new UploadTestHandler; - $this->hooks = $wgHooks; - $wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$data ) { - return false; - }; - } - - protected function tearDown() { - global $wgHooks; - $wgHooks = $this->hooks; - parent::tearDown(); + $this->setMwGlobals( 'wgHooks', array( + 'InterwikiLoadPrefix' => array( + function ( $prefix, &$data ) { + return false; + } + ), + ) ); } /** @@ -112,22 +108,246 @@ class UploadBaseTest extends MediaWikiTestCase { * This method should be abstracted so we can test different settings. */ public function testMaxUploadSize() { - global $wgMaxUploadSize; - $savedGlobal = $wgMaxUploadSize; // save global - global $wgFileExtensions; - $wgFileExtensions[] = 'txt'; - - $wgMaxUploadSize = 100; + $this->setMwGlobals( array( + 'wgMaxUploadSize' => 100, + 'wgFileExtensions' => array( + 'txt', + ), + ) ); - $filename = $this->createFileOfSize( $wgMaxUploadSize ); + $filename = $this->createFileOfSize( 100 ); $this->upload->initializePathInfo( basename( $filename ) . '.txt', $filename, 100 ); $result = $this->upload->verifyUpload(); unlink( $filename ); $this->assertEquals( - array( 'status' => UploadBase::OK ), $result ); + array( 'status' => UploadBase::OK ), + $result + ); + } + + + /** + * @dataProvider provideCheckSvgScriptCallback + */ + public function testCheckSvgScriptCallback( $svg, $wellFormed, $filterMatch, $message ) { + list( $formed, $match ) = $this->upload->checkSvgString( $svg ); + $this->assertSame( $wellFormed, $formed, $message ); + $this->assertSame( $filterMatch, $match, $message ); + } - $wgMaxUploadSize = $savedGlobal; // restore global + public static function provideCheckSvgScriptCallback() { + return array( + // html5sec SVG vectors + array( + '', + true, + true, + 'Script tag in svg (http://html5sec.org/#47)' + ), + array( + '', + true, + true, + 'SVG with onload property (http://html5sec.org/#11)' + ), + array( + '', + true, + true, + 'SVG with onload property (http://html5sec.org/#65)' + ), + array( + ' ', + true, + true, + 'SVG with javascript xlink (http://html5sec.org/#87)' + ), + array( + ' ', + true, + true, + 'SVG with Opera animation xlink (http://html5sec.org/#88 - a)' + ), + array( + ' ', + true, + true, + 'SVG with Opera animation xlink (http://html5sec.org/#88 - b)' + ), + array( + ' ', + true, + true, + 'SVG with Opera image xlink (http://html5sec.org/#88 - c)' + ), + array( + ' ', + true, + true, + 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - d)' + ), + array( + ' ', + true, + true, + 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - e)' + ), + array( + ' ', + true, + true, + 'SVG with event handler set (http://html5sec.org/#89 - a)' + ), + array( + ' ', + true, + true, + 'SVG with event handler animate (http://html5sec.org/#89 - a)' + ), + array( + ' alert(1) ', + true, + true, + 'SVG with element handler (http://html5sec.org/#94)' + ), + array( + ' ', + true, + true, + 'SVG with href to data: url (http://html5sec.org/#95)' + ), + array( + ' ', + true, + true, + 'SVG with Tiny handler (http://html5sec.org/#104)' + ), + array( + ' ', + true, + true, + 'SVG with new CSS styles properties (http://html5sec.org/#109)' + ), + array( + ' ', + true, + true, + 'SVG with new CSS styles properties as attributes' + ), + array( + ' ', + true, + true, + 'SVG with new CSS styles properties as attributes (2)' + ), + array( + ' ', + true, + true, + 'SVG with path marker-start (http://html5sec.org/#110)' + ), + array( + ' ]> ', + true, + true, + 'SVG with embedded stylesheet (http://html5sec.org/#125)' + ), + array( + ' alert(1) ', + true, + true, + 'SVG with handler attribute (http://html5sec.org/#127)' + ), + array( + // Haven't found a browser that accepts this particular example, but we + // don't want to allow embeded svgs, ever + ' ', + true, + true, + 'SVG with image filter via style (http://html5sec.org/#129)' + ), + array( + // This doesn't seem possible without embedding the svg, but just in case + ' ', + true, + true, + 'SVG with animate from (http://html5sec.org/#137)' + ), + + // Other hostile SVG's + array( + ' ', + true, + true, + 'SVG with non-local image href (bug 65839)' + ), + array( + ' 50 100 ', + true, + true, + 'SVG with remote stylesheet (bug 57550)' + ), + array( + ' B ', + true, + true, + 'SVG with rembeded iframe (bug 60771)' + ), + array( + ' WebPlatform.org ', + true, + true, + 'SVG with @import in style element (bug 69008)' + ), + array( + ' WebPlatform.org ', + true, + true, + 'SVG with @import in style element and child element (bug 69008#c11)' + ), + array( + ' ', + true, + true, + 'SVG with remote background image (bug 69008)' + ), + array( + ' ', + true, + true, + 'SVG with remote background image, encoded (bug 69008)' + ), + array( + ' ', + true, + true, + 'SVG with remote background image, in style element (bug 69008)' + ), + array( + // This currently doesn't seem to work in any browsers, but in case + // http://www.w3.org/TR/css3-images/ is implemented for SVG files + ' ', + true, + true, + 'SVG with remote background image using image() (bug 69008)' + ), + + // Test good, but strange files that we want to allow + array( + ' ', + true, + false, + 'SVG with link to a remote site' + ), + array( + ' 12345 ', + true, + false, + 'SVG with local urls, including filter: in style' + ), + + ); } } @@ -143,4 +363,19 @@ class UploadTestHandler extends UploadBase { return $this->mTitleError; } + + /** + * Almost the same as UploadBase::detectScriptInSvg, except it's + * public, works on an xml string instead of filename, and returns + * the result instead of interpreting them. + */ + public function checkSvgString( $svg ) { + $check = new XmlTypeCheck( + $svg, + array( $this, 'checkSvgScriptCallback' ), + false, + array( 'processing_instruction_handler' => 'UploadBase::checkSvgPICallback' ) + ); + return array( $check->wellFormed, $check->filterMatch ); + } } diff --git a/tests/phpunit/includes/utils/MWCryptHKDFTest.php b/tests/phpunit/includes/utils/MWCryptHKDFTest.php index 7e37534a77..73e4c1a909 100644 --- a/tests/phpunit/includes/utils/MWCryptHKDFTest.php +++ b/tests/phpunit/includes/utils/MWCryptHKDFTest.php @@ -6,6 +6,12 @@ class MWCryptHKDFTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgSecretKey', '5bf1945342e67799cb50704a7fa19ac6' ); + } + /** * Test basic usage works */ diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js index 76fa6bedb2..1d5656e971 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js @@ -61,44 +61,6 @@ } ); } ); - QUnit.test( 'Deprecated callback methods', function ( assert ) { - QUnit.expect( 3 ); - - var api = new mw.Api(); - - this.suppressWarnings(); - - api.get( {}, function () { - assert.ok( true, 'Function argument treated as success callback.' ); - } ); - - api.get( {}, { - ok: function () { - assert.ok( true, '"ok" property treated as success callback.' ); - } - } ); - - api.get( { action: 'doesntexist' }, { - err: function () { - assert.ok( true, '"err" property treated as error callback.' ); - } - } ); - - this.restoreWarnings(); - - this.server.respondWith( /action=query/, function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); - } ); - - this.server.respondWith( /action=doesntexist/, function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "unknown_action" } }' - ); - } ); - - this.server.respond(); - } ); - QUnit.test( 'getToken( pre-populated )', function ( assert ) { QUnit.expect( 2 ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 906fd27f49..ece5116a36 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -54,7 +54,9 @@ 'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]', - 'external-link-replace': 'Foo [$1 bar]' + 'external-link-replace': 'Foo [$1 bar]', + 'external-link-plural': 'Foo {{PLURAL:$1|is [$2 one]|are [$2 some]|2=[$2 two]|3=three|4=a=b|5=}} things.', + 'plural-only-explicit-forms': 'It is a {{PLURAL:$1|1=single|2=double}} room.' } } ) ); @@ -85,7 +87,7 @@ } ); } - QUnit.test( 'Replace', 9, function ( assert ) { + QUnit.test( 'Replace', 16, function ( assert ) { mw.messages.set( 'simple', 'Foo $1 baz $2' ); assert.equal( formatParse( 'simple' ), 'Foo $1 baz $2', 'Replacements with no substitutes' ); @@ -133,6 +135,41 @@ 'Foo bar', 'Href is not double-escaped in wikilink function' ); + assert.equal( + formatParse( 'external-link-plural', 1, 'http://example.org' ), + 'Foo is one things.', + 'Link is expanded inside plural and is not escaped html' + ); + assert.equal( + formatParse( 'external-link-plural', 2, 'http://example.org' ), + 'Foo two things.', + 'Link is expanded inside an explicit plural form and is not escaped html' + ); + assert.equal( + formatParse( 'external-link-plural', 3 ), + 'Foo three things.', + 'A simple explicit plural form co-existing with complex explicit plural forms' + ); + assert.equal( + formatParse( 'external-link-plural', 4, 'http://example.org' ), + 'Foo a=b things.', + 'Only first equal sign is used as delimiter for explicit plural form. Repeated equal signs does not create issue' + ); + assert.equal( + formatParse( 'external-link-plural', 5, 'http://example.org' ), + 'Foo are some things.', + 'Invalid explicit plural form. Plural fallback to the "other" plural form' + ); + assert.equal( + formatParse( 'external-link-plural', 6, 'http://example.org' ), + 'Foo are some things.', + 'Plural fallback to the "other" plural form' + ); + assert.equal( + formatParse( 'plural-only-explicit-forms', 2 ), + 'It is a double room.', + 'Plural with explicit forms alone.' + ); } ); QUnit.test( 'Plural', 6, function ( assert ) { diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index 4401eadb57..9b620de406 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -189,7 +189,7 @@ ); assert.equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' ); - assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (by passing nextnode)' ); + assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (nextnode as Node object)' ); cuQuux = mw.util.addPortletLink( 'p-test-custom', '#', 'Quux', null, 'Example [shift-x]', 'q' ); $cuQuux = $( cuQuux ); @@ -205,7 +205,7 @@ tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' ); - assert.equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' ); + assert.strictEqual( $( tbRLDM ).next()[0], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); @@ -213,19 +213,19 @@ assert.strictEqual( $( caFoo ).find( 'span' ).length, 1, 'A element should be added for porlets with vectorTabs class.' ); addedAfter = mw.util.addPortletLink( 'p-test-tb', '#', 'After foo', 'post-foo', 'After foo', null, $( tbRL ) ); - assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (by passing a jQuery object as nextnode)' ); + assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (jQuery object as nextnode)' ); // test case - nonexistent id as next node tbRLDMnonexistentid = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-nonexistent', 'List of all default modules ', 'd', '#t-rl-nonexistent' ); - assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Nonexistent id as nextnode adds the portlet at end' ); + assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode non-matching CSS selector)' ); // test case - empty jquery object as next node tbRLDMemptyjquery = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-empty-jquery', 'List of all default modules ', 'd', $( '#t-rl-nonexistent' ) ); - assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Empty jquery as nextnode adds the portlet at end' ); + assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode as empty jQuery object)' ); } ); QUnit.test( 'jsMessage', 1, function ( assert ) { diff --git a/tests/qunit/suites/resources/startup.test.js b/tests/qunit/suites/resources/startup.test.js index ed03418a9c..6011961ac5 100644 --- a/tests/qunit/suites/resources/startup.test.js +++ b/tests/qunit/suites/resources/startup.test.js @@ -96,6 +96,7 @@ 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20060928 (Debian|Debian-1.8.0.7-1) Epiphany/2.14', 'Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1.6) Gecko/20070817 IceWeasel/2.0.0.6-g2', // KHTML + 'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.4 (like Gecko)', 'Mozilla/5.0 (compatible; Konqueror/4.3; Linux) KHTML/4.3.5 (like Gecko)', // Text browsers 'Links (2.1pre33; Darwin 8.11.0 Power Macintosh; x)', diff --git a/tests/testHelpers.inc b/tests/testHelpers.inc index 62dccbf0b3..b5fc800326 100644 --- a/tests/testHelpers.inc +++ b/tests/testHelpers.inc @@ -558,7 +558,7 @@ class TestFileIterator implements Iterator { $line = trim( $line ); if ( $line ) { - $delayedParserTest->requireTransparentHook( $line ); + $this->delayedParserTest->requireTransparentHook( $line ); } } diff --git a/thumb.php b/thumb.php index d8ed246f96..3d8612d972 100644 --- a/thumb.php +++ b/thumb.php @@ -32,7 +32,7 @@ if ( defined( 'THUMB_HANDLER' ) ) { wfThumbHandle404(); } else { // Called directly, use $_GET params - wfThumbHandleRequest(); + wfStreamThumb( $_GET ); } wfLogProfilingData(); @@ -43,19 +43,6 @@ $factory->shutdown(); //-------------------------------------------------------------------------- -/** - * Handle a thumbnail request via query parameters - * - * @return void - */ -function wfThumbHandleRequest() { - $params = get_magic_quotes_gpc() - ? array_map( 'stripslashes', $_GET ) - : $_GET; - - wfStreamThumb( $params ); // stream the thumbnail -} - /** * Handle a thumbnail request via thumbnail file URL *