if ( $config->get( 'StructuredChangeFiltersShowPreference' ) ) {
$defaultPreferences['rcenhancedfilters-disable'] = [
'type' => 'toggle',
- 'section' => 'rc/advancedrc',
+ 'section' => 'rc/opt-out',
'label-message' => 'rcfilters-preference-label',
'help-message' => 'rcfilters-preference-help',
];
if ( $content !== false ) {
$s .= $content . "\n";
+ $s .= self::closeElement( 'fieldset' ) . "\n";
}
- $s .= self::closeElement( 'fieldset' ) . "\n";
return $s;
}
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'prop', 'generatexml' );
+ $title = $params['title'];
+ if ( $title === null ) {
+ $titleProvided = false;
+ // A title is needed for parsing, so arbitrarily choose one
+ $title = 'API';
+ } else {
+ $titleProvided = true;
+ }
+
if ( $params['prop'] === null ) {
$this->addDeprecation(
'apiwarn-deprecation-expandtemplates-prop', 'action=expandtemplates&!prop'
$prop = array_flip( $params['prop'] );
}
+ $titleObj = Title::newFromText( $title );
+ if ( !$titleObj || $titleObj->isExternal() ) {
+ $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
+ }
+
// Get title and revision ID for parser
$revid = $params['revid'];
if ( $revid !== null ) {
if ( !$rev ) {
$this->dieWithError( [ 'apierror-nosuchrevid', $revid ] );
}
- $title_obj = $rev->getTitle();
- } else {
- $title_obj = Title::newFromText( $params['title'] );
- if ( !$title_obj || $title_obj->isExternal() ) {
- $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
+ $pTitleObj = $titleObj;
+ $titleObj = $rev->getTitle();
+ if ( $titleProvided ) {
+ if ( !$titleObj->equals( $pTitleObj ) ) {
+ $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(),
+ wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] );
+ }
+ } else {
+ // Consider the title derived from the revid as having
+ // been provided.
+ $titleProvided = true;
}
}
$reset = null;
$suppressCache = false;
Hooks::run( 'ApiMakeParserOptions',
- [ $options, $title_obj, $params, $this, &$reset, &$suppressCache ] );
+ [ $options, $titleObj, $params, $this, &$reset, &$suppressCache ] );
$retval = [];
if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
- $wgParser->startExternalParse( $title_obj, $options, Parser::OT_PREPROCESS );
+ $wgParser->startExternalParse( $titleObj, $options, Parser::OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $params['text'] );
if ( is_callable( [ $dom, 'saveXML' ] ) ) {
$xml = $dom->saveXML();
// if they didn't want any output except (probably) the parse tree,
// then don't bother actually fully expanding it
if ( $prop || $params['prop'] === null ) {
- $wgParser->startExternalParse( $title_obj, $options, Parser::OT_PREPROCESS );
+ $wgParser->startExternalParse( $titleObj, $options, Parser::OT_PREPROCESS );
$frame = $wgParser->getPreprocessor()->newFrame();
- $wikitext = $wgParser->preprocess( $params['text'], $title_obj, $options, $revid, $frame );
+ $wikitext = $wgParser->preprocess( $params['text'], $titleObj, $options, $revid, $frame );
if ( $params['prop'] === null ) {
// the old way
ApiResult::setContentValue( $retval, 'wikitext', $wikitext );
public function getAllowedParams() {
return [
- 'title' => [
- ApiBase::PARAM_DFLT => 'API',
- ],
+ 'title' => null,
'text' => [
ApiBase::PARAM_TYPE => 'text',
ApiBase::PARAM_REQUIRED => true,
// Get parameters
$params = $this->extractRequestParams();
- // No easy way to say that text & title are allowed together while the
- // rest aren't, so just do it in two calls.
+ // No easy way to say that text and title or revid are allowed together
+ // while the rest aren't, so just do it in three calls.
$this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' );
$this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' );
+ $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'revid' );
$text = $params['text'];
$title = $params['title'];
if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
}
+ $revid = $params['revid'];
+ if ( $revid !== null ) {
+ $rev = Revision::newFromId( $revid );
+ if ( !$rev ) {
+ $this->dieWithError( [ 'apierror-nosuchrevid', $revid ] );
+ }
+ $pTitleObj = $titleObj;
+ $titleObj = $rev->getTitle();
+ if ( $titleProvided ) {
+ if ( !$titleObj->equals( $pTitleObj ) ) {
+ $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(),
+ wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] );
+ }
+ } else {
+ // Consider the title derived from the revid as having
+ // been provided.
+ $titleProvided = true;
+ }
+ }
$wgTitle = $titleObj;
if ( $titleObj->canExist() ) {
$pageObj = WikiPage::factory( $titleObj );
if ( !$textProvided ) {
if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
- $this->addWarning( 'apiwarn-parse-titlewithouttext' );
+ if ( $revid !== null ) {
+ $this->addWarning( 'apiwarn-parse-revidwithouttext' );
+ } else {
+ $this->addWarning( 'apiwarn-parse-titlewithouttext' );
+ }
}
// Prevent warning from ContentHandler::makeContent()
$text = '';
// Not cached (save or load)
if ( $params['pst'] ) {
- $p_result = $this->pstContent->getParserOutput( $titleObj, null, $popts );
+ $p_result = $this->pstContent->getParserOutput( $titleObj, $revid, $popts );
} else {
- $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
+ $p_result = $this->content->getParserOutput( $titleObj, $revid, $popts );
}
}
'text' => [
ApiBase::PARAM_TYPE => 'text',
],
+ 'revid' => [
+ ApiBase::PARAM_TYPE => 'integer',
+ ],
'summary' => null,
'page' => null,
'pageid' => [
"apihelp-expandtemplates-summary": "Expands all templates within wikitext.",
"apihelp-expandtemplates-param-title": "Title of page.",
"apihelp-expandtemplates-param-text": "Wikitext to convert.",
- "apihelp-expandtemplates-param-revid": "Revision ID, for <nowiki>{{REVISIONID}}</nowiki> and similar variables.",
+ "apihelp-expandtemplates-param-revid": "Revision ID, for <code><nowiki>{{REVISIONID}}</nowiki></code> and similar variables.",
"apihelp-expandtemplates-param-prop": "Which pieces of information to get.\n\nNote that if no values are selected, the result will contain the wikitext, but the output will be in a deprecated format.",
"apihelp-expandtemplates-paramvalue-prop-wikitext": "The expanded wikitext.",
"apihelp-expandtemplates-paramvalue-prop-categories": "Any categories present in the input that are not represented in the wikitext output.",
"apihelp-paraminfo-example-2": "Show info for all submodules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd>.",
"apihelp-parse-summary": "Parses content and returns parser output.",
- "apihelp-parse-extended-description": "See the various prop-modules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd> to get information from the current version of a page.\n\nThere are several ways to specify the text to parse:\n# Specify a page or revision, using <var>$1page</var>, <var>$1pageid</var>, or <var>$1oldid</var>.\n# Specify content explicitly, using <var>$1text</var>, <var>$1title</var>, and <var>$1contentmodel</var>.\n# Specify only a summary to parse. <var>$1prop</var> should be given an empty value.",
+ "apihelp-parse-extended-description": "See the various prop-modules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd> to get information from the current version of a page.\n\nThere are several ways to specify the text to parse:\n# Specify a page or revision, using <var>$1page</var>, <var>$1pageid</var>, or <var>$1oldid</var>.\n# Specify content explicitly, using <var>$1text</var>, <var>$1title</var>, <var>$1revid</var>, and <var>$1contentmodel</var>.\n# Specify only a summary to parse. <var>$1prop</var> should be given an empty value.",
"apihelp-parse-param-title": "Title of page the text belongs to. If omitted, <var>$1contentmodel</var> must be specified, and [[API]] will be used as the title.",
"apihelp-parse-param-text": "Text to parse. Use <var>$1title</var> or <var>$1contentmodel</var> to control the content model.",
+ "apihelp-parse-param-revid": "Revision ID, for <code><nowiki>{{REVISIONID}}</nowiki></code> and similar variables.",
"apihelp-parse-param-summary": "Summary to parse.",
"apihelp-parse-param-page": "Parse the content of this page. Cannot be used together with <var>$1text</var> and <var>$1title</var>.",
"apihelp-parse-param-pageid": "Parse the content of this page. Overrides <var>$1page</var>.",
"apiwarn-notfile": "\"$1\" is not a file.",
"apiwarn-nothumb-noimagehandler": "Could not create thumbnail because $1 does not have an associated image handler.",
"apiwarn-parse-nocontentmodel": "No <var>title</var> or <var>contentmodel</var> was given, assuming $1.",
+ "apiwarn-parse-revidwithouttext": "<var>revid</var> used without <var>text</var>, and parsed page properties were requested. Did you mean to use <var>oldid</var> instead of <var>revid</var>?",
"apiwarn-parse-titlewithouttext": "<var>title</var> used without <var>text</var>, and parsed page properties were requested. Did you mean to use <var>page</var> instead of <var>title</var>?",
"apiwarn-redirectsandrevids": "Redirect resolution cannot be used together with the <var>revids</var> parameter. Any redirects the <var>revids</var> point to have not been resolved.",
"apiwarn-tokennotallowed": "Action \"$1\" is not allowed for the current user.",
"apihelp-expandtemplates-summary": "{{doc-apihelp-summary|expandtemplates}}",
"apihelp-expandtemplates-param-title": "{{doc-apihelp-param|expandtemplates|title}}",
"apihelp-expandtemplates-param-text": "{{doc-apihelp-param|expandtemplates|text}}",
- "apihelp-expandtemplates-param-revid": "{{doc-apihelp-param|expandtemplates|revid}}\n{{doc-important|Do not translate <code><<nowiki />nowiki>{{<nowiki />REVISIONID}}<<nowiki />/nowiki></code>}}",
+ "apihelp-expandtemplates-param-revid": "{{doc-apihelp-param|expandtemplates|revid}}",
"apihelp-expandtemplates-param-prop": "{{doc-apihelp-param|expandtemplates|prop|paramvalues=1}}",
"apihelp-expandtemplates-paramvalue-prop-wikitext": "{{doc-apihelp-paramvalue|expandtemplates|prop|wikitext}}",
"apihelp-expandtemplates-paramvalue-prop-categories": "{{doc-apihelp-paramvalue|expandtemplates|prop|categories}}",
"apihelp-parse-extended-description": "{{doc-apihelp-extended-description|parse}}",
"apihelp-parse-param-title": "{{doc-apihelp-param|parse|title}}",
"apihelp-parse-param-text": "{{doc-apihelp-param|parse|text}}",
+ "apihelp-parse-param-revid": "{{doc-apihelp-param|parse|revid}}",
"apihelp-parse-param-summary": "{{doc-apihelp-param|parse|summary}}",
"apihelp-parse-param-page": "{{doc-apihelp-param|parse|page}}",
"apihelp-parse-param-pageid": "{{doc-apihelp-param|parse|pageid}}",
"apiwarn-notfile": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied file name.",
"apiwarn-nothumb-noimagehandler": "{{doc-apierror}}\n\nParameters:\n* $1 - File name.",
"apiwarn-parse-nocontentmodel": "{{doc-apierror}}\n\nParameters:\n* $1 - Content model being assumed.",
+ "apiwarn-parse-revidwithouttext": "{{doc-apierror}}",
"apiwarn-parse-titlewithouttext": "{{doc-apierror}}",
"apiwarn-redirectsandrevids": "{{doc-apierror}}",
"apiwarn-tokennotallowed": "{{doc-apierror}}\n\nParameters:\n* $1 - Token type being requested, typically named after the action requiring the token.",
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
$comment = Xml::element( 'comment', [ 'deleted' => 'deleted' ] );
} else {
- $comment = Xml::elementClean( 'comment', null, $file->getDescription() );
+ $comment = Xml::elementClean( 'comment', null, strval( $file->getDescription() ) );
}
return " <upload>\n" .
$this->writeTimestamp( $file->getTimestamp() ) .
static $results = [];
if ( $prefix == '' ) {
- return $fields;
+ return array_merge( $fields, [ 'description' ] );
}
-
if ( !isset( $results[$prefix] ) ) {
$prefixedFields = [];
foreach ( $fields as $field ) {
// 1.25
// note this patch covers other _comment and _description fields too
- [ 'modifyField', 'recentchanges', 'rc_comment', 'patch-editsummary-length.sql' ],
+ [ 'doExtendCommentLengths' ],
// 1.26
[ 'dropTable', 'hitcounter' ],
);
}
+ protected function doExtendCommentLengths() {
+ $table = $this->db->tableName( 'revision' );
+ $res = $this->db->query( "SHOW COLUMNS FROM $table LIKE 'rev_comment'" );
+ $row = $this->db->fetchObject( $res );
+
+ if ( $row && ( $row->Type !== "varbinary(767)" || $row->Default !== "" ) ) {
+ $this->applyPatch(
+ 'patch-editsummary-length.sql',
+ false,
+ 'Extending edit summary lengths (and setting defaults)'
+ );
+ } else {
+ $this->output( '...comment fields are up to date' );
+ }
+ }
+
public function getSchemaVars() {
global $wgDBTableOptions;
if ( preg_match( '/^[\r\n\t\x20-\x7e]+$/', $contents ) ) {
// Do not base64-encode non-binary files (sane SVGs).
// (This often produces longer URLs, but they compress better, yielding a net smaller size.)
- $uri = 'data:' . $type . ',' . rawurlencode( $contents );
+ $encoded = rawurlencode( $contents );
+ // Unencode some things that don't need to be encoded, to make the encoding smaller
+ $encoded = strtr( $encoded, [
+ '%20' => ' ', // Unencode spaces
+ '%2F' => '/', // Unencode slashes
+ '%3A' => ':', // Unencode colons
+ '%3D' => '=', // Unencode equals signs
+ ] );
+ $uri = 'data:' . $type . ',' . $encoded;
if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
return $uri;
}
* @return string[] CSS declarations to use given URIs as background-image
*/
protected function getCssDeclarations( $primary, $fallback ) {
+ $primaryUrl = CSSMin::buildUrlValue( $primary );
+ $fallbackUrl = CSSMin::buildUrlValue( $fallback );
return [
- "background-image: url($fallback);",
- "background-image: linear-gradient(transparent, transparent), url($primary);",
+ "background-image: $fallbackUrl;",
+ "background-image: linear-gradient(transparent, transparent), $primaryUrl;",
// Do not serve SVG to Opera 12, bad rendering with border-radius or background-size (T87504)
- "background-image: -o-linear-gradient(transparent, transparent), url($fallback);",
+ "background-image: -o-linear-gradient(transparent, transparent), $fallbackUrl;",
];
}
}
);
$htmlForm->setMethod( 'get' );
-
- $out->addHTML( Xml::fieldset( $this->msg( 'newpages' )->text() ) );
-
+ $htmlForm->setWrapperLegend( true );
+ $htmlForm->setWrapperLegendMsg( 'newpages' );
+ $htmlForm->addFooterText( Html::rawElement(
+ 'div',
+ null,
+ $this->filterLinks()
+ ) );
$htmlForm->show();
-
- $out->addHTML(
- Html::rawElement(
- 'div',
- null,
- $this->filterLinks()
- ) .
- Xml::closeElement( 'fieldset' )
- );
}
/**
"prefs-editor": "Editor",
"prefs-preview": "Preview",
"prefs-advancedrc": "Advanced options",
+ "prefs-opt-out": "Opt out of improvements",
"prefs-advancedrendering": "Advanced options",
"prefs-advancedsearchoptions": "Advanced options",
"prefs-advancedwatchlist": "Advanced options",
"prefs-editor": "Used in [[Special:Preferences]], tab \"Editing\" ({{int:prefs-editing}}).\n\n{{Identical|Editor}}",
"prefs-preview": "Used in [[Special:Preferences]], tab \"Editing\".\n{{Identical|Preview}}",
"prefs-advancedrc": "Used in [[Special:Preferences]], tab \"Recent changes\".\n{{Identical|Advanced options}}",
+ "prefs-opt-out": "Used in [[Special:Preferences]], tab \"Recent changes\".",
"prefs-advancedrendering": "Used in [[Special:Preferences]], tab \"Appearence\".\n{{Identical|Advanced options}}",
"prefs-advancedsearchoptions": "Used in [[Special:Preferences]], tab \"Search options\".\n{{Identical|Advanced options}}",
"prefs-advancedwatchlist": "Used in [[Special:Preferences]], tab \"Watchlist\".\n{{Identical|Advanced options}}",
-ALTER TABLE /*_*/revision MODIFY rev_comment varbinary(767) NOT NULL;
-ALTER TABLE /*_*/archive MODIFY ar_comment varbinary(767) NOT NULL;
-ALTER TABLE /*_*/image MODIFY img_description varbinary(767) NOT NULL;
-ALTER TABLE /*_*/oldimage MODIFY oi_description varbinary(767) NOT NULL;
-ALTER TABLE /*_*/filearchive MODIFY fa_description varbinary(767);
+ALTER TABLE /*_*/revision MODIFY rev_comment varbinary(767) NOT NULL default '';
+ALTER TABLE /*_*/archive MODIFY ar_comment varbinary(767) NOT NULL default '';
+ALTER TABLE /*_*/image MODIFY img_description varbinary(767) NOT NULL default '';
+ALTER TABLE /*_*/oldimage MODIFY oi_description varbinary(767) NOT NULL default '';
+ALTER TABLE /*_*/filearchive MODIFY fa_description varbinary(767) default '';
ALTER TABLE /*_*/filearchive MODIFY fa_deleted_reason varbinary(767) default '';
ALTER TABLE /*_*/recentchanges MODIFY rc_comment varbinary(767) NOT NULL default '';
ALTER TABLE /*_*/logging MODIFY log_comment varbinary(767) NOT NULL default '';
-ALTER TABLE /*_*/ipblocks MODIFY ipb_reason varbinary(767) NOT NULL;
-ALTER TABLE /*_*/protected_titles MODIFY pt_reason varbinary(767);
+ALTER TABLE /*_*/ipblocks MODIFY ipb_reason varbinary(767) NOT NULL default '';
+ALTER TABLE /*_*/protected_titles MODIFY pt_reason varbinary(767) default '';
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8">
-<circle cx="4" cy="4" r="2"/>
+ <circle cx="4" cy="4" r="2"/>
</svg>
TEXT
);
$this->addOption( 'rev-id', 'The rev_id to start copying from. Default: 0', false, true );
+ $this->addOption(
+ 'max-rev-id',
+ 'The rev_id to stop at. Default: result of MAX(rev_id)',
+ false,
+ true
+ );
$this->addOption(
'throttle',
'Wait this many milliseconds after copying each batch of revisions. Default: 0',
public function doDBUpdates() {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
$dbw = $this->getDB( DB_MASTER );
$throttle = intval( $this->getOption( 'throttle', 0 ) );
+ $maxRevId = intval( $this->getOption( 'max-rev-id', 0 ) );
$start = $this->getOption( 'rev-id', 0 );
- $end = $dbw->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ );
+ $end = $maxRevId > 0
+ ? $maxRevId
+ : $dbw->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ );
$blockStart = $start;
$revCount = 0;
$this->output( "Copying IP revisions to ip_changes, from rev_id $start to rev_id $end\n" );
while ( $blockStart <= $end ) {
- $rows = $dbw->select(
+ $blockEnd = min( $blockStart + 200, $end );
+ $rows = $dbr->select(
'revision',
[ 'rev_id', 'rev_timestamp', 'rev_user_text' ],
- [ "rev_id >= $blockStart", 'rev_user' => 0 ],
+ [ "rev_id BETWEEN $blockStart AND $blockEnd", 'rev_user' => 0 ],
__METHOD__,
[ 'ORDER BY' => 'rev_id ASC', 'LIMIT' => $this->mBatchSize ]
);
}
$this->output( "...checking $this->mBatchSize revisions for IP edits that need copying, " .
- "starting with rev_id $blockStart\n" );
+ "between rev_ids $blockStart and $blockEnd\n" );
$insertRows = [];
foreach ( $rows as $row ) {
'styles' => 'resources/src/mediawiki.special/mediawiki.special.pagesWithProp.css',
],
'mediawiki.special.preferences' => [
- 'scripts' => 'resources/src/mediawiki.special/mediawiki.special.preferences.js',
+ 'scripts' => [
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.convertmessagebox.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js',
+ ],
'messages' => [
'prefs-tabs-navigation-hint',
'prefswarning-warning',
--- /dev/null
+/*!
+ * JavaScript for Special:Preferences: Enable save button and prevent the window being accidentally
+ * closed when any form field is changed.
+ */
+( function ( mw, $ ) {
+ $( function () {
+ var allowCloseWindow;
+
+ // Check if all of the form values are unchanged
+ function isPrefsChanged() {
+ var inputs = $( '#mw-prefs-form :input[name]' ),
+ input, $input, inputType,
+ index, optIndex,
+ opt;
+
+ for ( index = 0; index < inputs.length; index++ ) {
+ input = inputs[ index ];
+ $input = $( input );
+
+ // Different types of inputs have different methods for accessing defaults
+ if ( $input.is( 'select' ) ) {
+ // <select> has the property defaultSelected for each option
+ for ( optIndex = 0; optIndex < input.options.length; optIndex++ ) {
+ opt = input.options[ optIndex ];
+ if ( opt.selected !== opt.defaultSelected ) {
+ return true;
+ }
+ }
+ } else if ( $input.is( 'input' ) ) { // <input> has defaultValue or defaultChecked
+ inputType = input.type;
+ if ( inputType === 'radio' || inputType === 'checkbox' ) {
+ if ( input.checked !== input.defaultChecked ) {
+ return true;
+ }
+ } else if ( input.value !== input.defaultValue ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ // Disable the button to save preferences unless preferences have changed
+ // Check if preferences have been changed before JS has finished loading
+ if ( !isPrefsChanged() ) {
+ $( '#prefcontrol' ).prop( 'disabled', true );
+ $( '#preferences > fieldset' ).one( 'change keydown mousedown', function () {
+ $( '#prefcontrol' ).prop( 'disabled', false );
+ } );
+ }
+
+ // Set up a message to notify users if they try to leave the page without
+ // saving.
+ allowCloseWindow = mw.confirmCloseWindow( {
+ test: isPrefsChanged,
+ message: mw.msg( 'prefswarning-warning', mw.msg( 'saveprefs' ) ),
+ namespace: 'prefswarning'
+ } );
+ $( '#mw-prefs-form' ).submit( $.proxy( allowCloseWindow, 'release' ) );
+ $( '#mw-prefs-restoreprefs' ).click( $.proxy( allowCloseWindow, 'release' ) );
+ } );
+}( mediaWiki, jQuery ) );
--- /dev/null
+/*!
+ * JavaScript for Special:Preferences: Check for successbox to replace with notifications.
+ */
+( function ( mw, $ ) {
+ $( function () {
+ var convertmessagebox = require( 'mediawiki.notification.convertmessagebox' );
+ convertmessagebox();
+ } );
+}( mediaWiki, jQuery ) );
+++ /dev/null
-/*!
- * JavaScript for Special:Preferences
- */
-( function ( mw, $ ) {
- $( function () {
- var $preftoc, $preferences, $fieldsets, labelFunc, previousTab,
- $tzSelect, $tzTextbox, $localtimeHolder, servertime, allowCloseWindow,
- convertmessagebox = require( 'mediawiki.notification.convertmessagebox' );
-
- labelFunc = function () {
- return this.id.replace( /^mw-prefsection/g, 'preftab' );
- };
-
- $preftoc = $( '#preftoc' );
- $preferences = $( '#preferences' );
-
- $fieldsets = $preferences.children( 'fieldset' )
- .attr( {
- role: 'tabpanel',
- 'aria-labelledby': labelFunc
- } );
- $fieldsets.not( '#mw-prefsection-personal' )
- .hide()
- .attr( 'aria-hidden', 'true' );
-
- // T115692: The following is kept for backwards compatibility with older skins
- $preferences.addClass( 'jsprefs' );
- $fieldsets.addClass( 'prefsection' );
- $fieldsets.children( 'legend' ).addClass( 'mainLegend' );
-
- // Make sure the accessibility tip is selectable so that screen reader users take notice,
- // but hide it per default to reduce interface clutter. Also make sure it becomes visible
- // when selected. Similar to jquery.mw-jump
- $( '<div>' ).addClass( 'mw-navigation-hint' )
- .text( mw.msg( 'prefs-tabs-navigation-hint' ) )
- .attr( 'tabIndex', 0 )
- .on( 'focus blur', function ( e ) {
- if ( e.type === 'blur' || e.type === 'focusout' ) {
- $( this ).css( 'height', '0' );
- } else {
- $( this ).css( 'height', 'auto' );
- }
- } ).insertBefore( $preftoc );
-
- /**
- * It uses document.getElementById for security reasons (HTML injections in $()).
- *
- * @ignore
- * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
- * @param {string} [mode] A hash will be set according to the current
- * open section. Set mode 'noHash' to surpress this.
- */
- function switchPrefTab( name, mode ) {
- var $tab, scrollTop;
- // Handle hash manually to prevent jumping,
- // therefore save and restore scrollTop to prevent jumping.
- scrollTop = $( window ).scrollTop();
- if ( mode !== 'noHash' ) {
- location.hash = '#mw-prefsection-' + name;
- }
- $( window ).scrollTop( scrollTop );
-
- $preftoc.find( 'li' ).removeClass( 'selected' )
- .find( 'a' ).attr( {
- tabIndex: -1,
- 'aria-selected': 'false'
- } );
-
- $tab = $( document.getElementById( 'preftab-' + name ) );
- if ( $tab.length ) {
- $tab.attr( {
- tabIndex: 0,
- 'aria-selected': 'true'
- } ).focus()
- .parent().addClass( 'selected' );
-
- $preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' );
- $( document.getElementById( 'mw-prefsection-' + name ) ).show().attr( 'aria-hidden', 'false' );
- }
- }
-
- // Check for successbox to replace with notifications
- convertmessagebox();
-
- // Enable keyboard users to use left and right keys to switch tabs
- $preftoc.on( 'keydown', function ( event ) {
- var keyLeft = 37,
- keyRight = 39,
- $el;
-
- if ( event.keyCode === keyLeft ) {
- $el = $( '#preftoc li.selected' ).prev().find( 'a' );
- } else if ( event.keyCode === keyRight ) {
- $el = $( '#preftoc li.selected' ).next().find( 'a' );
- } else {
- return;
- }
- if ( $el.length > 0 ) {
- switchPrefTab( $el.attr( 'href' ).replace( '#mw-prefsection-', '' ) );
- }
- } );
-
- // Jump to correct section as indicated by the hash.
- // This function is called onload and onhashchange.
- function detectHash() {
- var hash = location.hash,
- matchedElement, parentSection;
- if ( hash.match( /^#mw-prefsection-[\w]+$/ ) ) {
- mw.storage.session.remove( 'mwpreferences-prevTab' );
- switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
- } else if ( hash.match( /^#mw-[\w-]+$/ ) ) {
- matchedElement = document.getElementById( hash.slice( 1 ) );
- parentSection = $( matchedElement ).parent().closest( '[id^="mw-prefsection-"]' );
- if ( parentSection.length ) {
- mw.storage.session.remove( 'mwpreferences-prevTab' );
- // Switch to proper tab and scroll to selected item.
- switchPrefTab( parentSection.attr( 'id' ).replace( 'mw-prefsection-', '' ), 'noHash' );
- matchedElement.scrollIntoView();
- }
- }
- }
-
- // In browsers that support the onhashchange event we will not bind click
- // handlers and instead let the browser do the default behavior (clicking the
- // <a href="#.."> will naturally set the hash, handled by onhashchange.
- // But other things that change the hash will also be caught (e.g. using
- // the Back and Forward browser navigation).
- // Note the special check for IE "compatibility" mode.
- if ( 'onhashchange' in window &&
- ( document.documentMode === undefined || document.documentMode >= 8 )
- ) {
- $( window ).on( 'hashchange', function () {
- var hash = location.hash;
- if ( hash.match( /^#mw-[\w-]+/ ) ) {
- detectHash();
- } else if ( hash === '' ) {
- switchPrefTab( 'personal', 'noHash' );
- }
- } )
- // Run the function immediately to select the proper tab on startup.
- .trigger( 'hashchange' );
- // In older browsers we'll bind a click handler as fallback.
- // We must not have onhashchange *and* the click handlers, otherwise
- // the click handler calls switchPrefTab() which sets the hash value,
- // which triggers onhashchange and calls switchPrefTab() again.
- } else {
- $preftoc.on( 'click', 'li a', function ( e ) {
- switchPrefTab( $( this ).attr( 'href' ).replace( '#mw-prefsection-', '' ) );
- e.preventDefault();
- } );
- // If we've reloaded the page or followed an open-in-new-window,
- // make the selected tab visible.
- detectHash();
- }
-
- // Timezone functions.
- // Guesses Timezone from browser and updates fields onchange.
-
- $tzSelect = $( '#mw-input-wptimecorrection' );
- $tzTextbox = $( '#mw-input-wptimecorrection-other' );
- $localtimeHolder = $( '#wpLocalTime' );
- servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 10 );
-
- function minutesToHours( min ) {
- var tzHour = Math.floor( Math.abs( min ) / 60 ),
- tzMin = Math.abs( min ) % 60,
- tzString = ( ( min >= 0 ) ? '' : '-' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour +
- ':' + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin;
- return tzString;
- }
-
- function hoursToMinutes( hour ) {
- var minutes,
- arr = hour.split( ':' );
-
- arr[ 0 ] = parseInt( arr[ 0 ], 10 );
-
- if ( arr.length === 1 ) {
- // Specification is of the form [-]XX
- minutes = arr[ 0 ] * 60;
- } else {
- // Specification is of the form [-]XX:XX
- minutes = Math.abs( arr[ 0 ] ) * 60 + parseInt( arr[ 1 ], 10 );
- if ( arr[ 0 ] < 0 ) {
- minutes *= -1;
- }
- }
- // Gracefully handle non-numbers.
- if ( isNaN( minutes ) ) {
- return 0;
- } else {
- return minutes;
- }
- }
-
- function updateTimezoneSelection() {
- var minuteDiff, localTime,
- type = $tzSelect.val();
-
- if ( type === 'other' ) {
- // User specified time zone manually in <input>
- // Grab data from the textbox, parse it.
- minuteDiff = hoursToMinutes( $tzTextbox.val() );
- } else {
- // Time zone not manually specified by user
- if ( type === 'guess' ) {
- // Get browser timezone & fill it in
- minuteDiff = -( new Date().getTimezoneOffset() );
- $tzTextbox.val( minutesToHours( minuteDiff ) );
- $tzSelect.val( 'other' );
- $tzTextbox.prop( 'disabled', false );
- } else {
- // Grab data from the $tzSelect value
- minuteDiff = parseInt( type.split( '|' )[ 1 ], 10 ) || 0;
- $tzTextbox.val( minutesToHours( minuteDiff ) );
- }
-
- // Set defaultValue prop on the generated box so we don't trigger the
- // unsaved preferences check
- $tzTextbox.prop( 'defaultValue', $tzTextbox.val() );
- }
-
- // Determine local time from server time and minutes difference, for display.
- localTime = servertime + minuteDiff;
-
- // Bring time within the [0,1440) range.
- localTime = ( ( localTime % 1440 ) + 1440 ) % 1440;
-
- $localtimeHolder.text( mw.language.convertNumber( minutesToHours( localTime ) ) );
- }
-
- if ( $tzSelect.length && $tzTextbox.length ) {
- $tzSelect.change( updateTimezoneSelection );
- $tzTextbox.blur( updateTimezoneSelection );
- updateTimezoneSelection();
- }
-
- // Restore the active tab after saving the preferences
- previousTab = mw.storage.session.get( 'mwpreferences-prevTab' );
- if ( previousTab ) {
- switchPrefTab( previousTab, 'noHash' );
- // Deleting the key, the tab states should be reset until we press Save
- mw.storage.session.remove( 'mwpreferences-prevTab' );
- }
-
- $( '#mw-prefs-form' ).on( 'submit', function () {
- var value = $( $preftoc ).find( 'li.selected a' ).attr( 'id' ).replace( 'preftab-', '' );
- mw.storage.session.set( 'mwpreferences-prevTab', value );
- } );
-
- // Check if all of the form values are unchanged
- function isPrefsChanged() {
- var inputs = $( '#mw-prefs-form :input[name]' ),
- input, $input, inputType,
- index, optIndex,
- opt;
-
- for ( index = 0; index < inputs.length; index++ ) {
- input = inputs[ index ];
- $input = $( input );
-
- // Different types of inputs have different methods for accessing defaults
- if ( $input.is( 'select' ) ) {
- // <select> has the property defaultSelected for each option
- for ( optIndex = 0; optIndex < input.options.length; optIndex++ ) {
- opt = input.options[ optIndex ];
- if ( opt.selected !== opt.defaultSelected ) {
- return true;
- }
- }
- } else if ( $input.is( 'input' ) ) { // <input> has defaultValue or defaultChecked
- inputType = input.type;
- if ( inputType === 'radio' || inputType === 'checkbox' ) {
- if ( input.checked !== input.defaultChecked ) {
- return true;
- }
- } else if ( input.value !== input.defaultValue ) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- // Disable the button to save preferences unless preferences have changed
- // Check if preferences have been changed before JS has finished loading
- if ( !isPrefsChanged() ) {
- $( '#prefcontrol' ).prop( 'disabled', true );
- $( '#preferences > fieldset' ).one( 'change keydown mousedown', function () {
- $( '#prefcontrol' ).prop( 'disabled', false );
- } );
- }
-
- // Set up a message to notify users if they try to leave the page without
- // saving.
- allowCloseWindow = mw.confirmCloseWindow( {
- test: isPrefsChanged,
- message: mw.msg( 'prefswarning-warning', mw.msg( 'saveprefs' ) ),
- namespace: 'prefswarning'
- } );
- $( '#mw-prefs-form' ).submit( $.proxy( allowCloseWindow, 'release' ) );
- $( '#mw-prefs-restoreprefs' ).click( $.proxy( allowCloseWindow, 'release' ) );
- } );
-}( mediaWiki, jQuery ) );
--- /dev/null
+/*!
+ * JavaScript for Special:Preferences: Tab navigation.
+ */
+( function ( mw, $ ) {
+ $( function () {
+ var $preftoc, $preferences, $fieldsets, labelFunc, previousTab;
+
+ labelFunc = function () {
+ return this.id.replace( /^mw-prefsection/g, 'preftab' );
+ };
+
+ $preftoc = $( '#preftoc' );
+ $preferences = $( '#preferences' );
+
+ $fieldsets = $preferences.children( 'fieldset' )
+ .attr( {
+ role: 'tabpanel',
+ 'aria-labelledby': labelFunc
+ } );
+ $fieldsets.not( '#mw-prefsection-personal' )
+ .hide()
+ .attr( 'aria-hidden', 'true' );
+
+ // T115692: The following is kept for backwards compatibility with older skins
+ $preferences.addClass( 'jsprefs' );
+ $fieldsets.addClass( 'prefsection' );
+ $fieldsets.children( 'legend' ).addClass( 'mainLegend' );
+
+ // Make sure the accessibility tip is selectable so that screen reader users take notice,
+ // but hide it per default to reduce interface clutter. Also make sure it becomes visible
+ // when selected. Similar to jquery.mw-jump
+ $( '<div>' ).addClass( 'mw-navigation-hint' )
+ .text( mw.msg( 'prefs-tabs-navigation-hint' ) )
+ .attr( 'tabIndex', 0 )
+ .on( 'focus blur', function ( e ) {
+ if ( e.type === 'blur' || e.type === 'focusout' ) {
+ $( this ).css( 'height', '0' );
+ } else {
+ $( this ).css( 'height', 'auto' );
+ }
+ } ).insertBefore( $preftoc );
+
+ /**
+ * It uses document.getElementById for security reasons (HTML injections in $()).
+ *
+ * @ignore
+ * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
+ * @param {string} [mode] A hash will be set according to the current
+ * open section. Set mode 'noHash' to surpress this.
+ */
+ function switchPrefTab( name, mode ) {
+ var $tab, scrollTop;
+ // Handle hash manually to prevent jumping,
+ // therefore save and restore scrollTop to prevent jumping.
+ scrollTop = $( window ).scrollTop();
+ if ( mode !== 'noHash' ) {
+ location.hash = '#mw-prefsection-' + name;
+ }
+ $( window ).scrollTop( scrollTop );
+
+ $preftoc.find( 'li' ).removeClass( 'selected' )
+ .find( 'a' ).attr( {
+ tabIndex: -1,
+ 'aria-selected': 'false'
+ } );
+
+ $tab = $( document.getElementById( 'preftab-' + name ) );
+ if ( $tab.length ) {
+ $tab.attr( {
+ tabIndex: 0,
+ 'aria-selected': 'true'
+ } ).focus()
+ .parent().addClass( 'selected' );
+
+ $preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' );
+ $( document.getElementById( 'mw-prefsection-' + name ) ).show().attr( 'aria-hidden', 'false' );
+ }
+ }
+
+ // Enable keyboard users to use left and right keys to switch tabs
+ $preftoc.on( 'keydown', function ( event ) {
+ var keyLeft = 37,
+ keyRight = 39,
+ $el;
+
+ if ( event.keyCode === keyLeft ) {
+ $el = $( '#preftoc li.selected' ).prev().find( 'a' );
+ } else if ( event.keyCode === keyRight ) {
+ $el = $( '#preftoc li.selected' ).next().find( 'a' );
+ } else {
+ return;
+ }
+ if ( $el.length > 0 ) {
+ switchPrefTab( $el.attr( 'href' ).replace( '#mw-prefsection-', '' ) );
+ }
+ } );
+
+ // Jump to correct section as indicated by the hash.
+ // This function is called onload and onhashchange.
+ function detectHash() {
+ var hash = location.hash,
+ matchedElement, parentSection;
+ if ( hash.match( /^#mw-prefsection-[\w]+$/ ) ) {
+ mw.storage.session.remove( 'mwpreferences-prevTab' );
+ switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
+ } else if ( hash.match( /^#mw-[\w-]+$/ ) ) {
+ matchedElement = document.getElementById( hash.slice( 1 ) );
+ parentSection = $( matchedElement ).parent().closest( '[id^="mw-prefsection-"]' );
+ if ( parentSection.length ) {
+ mw.storage.session.remove( 'mwpreferences-prevTab' );
+ // Switch to proper tab and scroll to selected item.
+ switchPrefTab( parentSection.attr( 'id' ).replace( 'mw-prefsection-', '' ), 'noHash' );
+ matchedElement.scrollIntoView();
+ }
+ }
+ }
+
+ // In browsers that support the onhashchange event we will not bind click
+ // handlers and instead let the browser do the default behavior (clicking the
+ // <a href="#.."> will naturally set the hash, handled by onhashchange.
+ // But other things that change the hash will also be caught (e.g. using
+ // the Back and Forward browser navigation).
+ // Note the special check for IE "compatibility" mode.
+ if ( 'onhashchange' in window &&
+ ( document.documentMode === undefined || document.documentMode >= 8 )
+ ) {
+ $( window ).on( 'hashchange', function () {
+ var hash = location.hash;
+ if ( hash.match( /^#mw-[\w-]+/ ) ) {
+ detectHash();
+ } else if ( hash === '' ) {
+ switchPrefTab( 'personal', 'noHash' );
+ }
+ } )
+ // Run the function immediately to select the proper tab on startup.
+ .trigger( 'hashchange' );
+ // In older browsers we'll bind a click handler as fallback.
+ // We must not have onhashchange *and* the click handlers, otherwise
+ // the click handler calls switchPrefTab() which sets the hash value,
+ // which triggers onhashchange and calls switchPrefTab() again.
+ } else {
+ $preftoc.on( 'click', 'li a', function ( e ) {
+ switchPrefTab( $( this ).attr( 'href' ).replace( '#mw-prefsection-', '' ) );
+ e.preventDefault();
+ } );
+ // If we've reloaded the page or followed an open-in-new-window,
+ // make the selected tab visible.
+ detectHash();
+ }
+
+ // Restore the active tab after saving the preferences
+ previousTab = mw.storage.session.get( 'mwpreferences-prevTab' );
+ if ( previousTab ) {
+ switchPrefTab( previousTab, 'noHash' );
+ // Deleting the key, the tab states should be reset until we press Save
+ mw.storage.session.remove( 'mwpreferences-prevTab' );
+ }
+
+ $( '#mw-prefs-form' ).on( 'submit', function () {
+ var value = $( $preftoc ).find( 'li.selected a' ).attr( 'id' ).replace( 'preftab-', '' );
+ mw.storage.session.set( 'mwpreferences-prevTab', value );
+ } );
+
+ } );
+}( mediaWiki, jQuery ) );
--- /dev/null
+/*!
+ * JavaScript for Special:Preferences: Timezone field enhancements.
+ */
+( function ( mw, $ ) {
+ $( function () {
+ var
+ $tzSelect, $tzTextbox, $localtimeHolder, servertime;
+
+ // Timezone functions.
+ // Guesses Timezone from browser and updates fields onchange.
+
+ $tzSelect = $( '#mw-input-wptimecorrection' );
+ $tzTextbox = $( '#mw-input-wptimecorrection-other' );
+ $localtimeHolder = $( '#wpLocalTime' );
+ servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 10 );
+
+ function minutesToHours( min ) {
+ var tzHour = Math.floor( Math.abs( min ) / 60 ),
+ tzMin = Math.abs( min ) % 60,
+ tzString = ( ( min >= 0 ) ? '' : '-' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour +
+ ':' + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin;
+ return tzString;
+ }
+
+ function hoursToMinutes( hour ) {
+ var minutes,
+ arr = hour.split( ':' );
+
+ arr[ 0 ] = parseInt( arr[ 0 ], 10 );
+
+ if ( arr.length === 1 ) {
+ // Specification is of the form [-]XX
+ minutes = arr[ 0 ] * 60;
+ } else {
+ // Specification is of the form [-]XX:XX
+ minutes = Math.abs( arr[ 0 ] ) * 60 + parseInt( arr[ 1 ], 10 );
+ if ( arr[ 0 ] < 0 ) {
+ minutes *= -1;
+ }
+ }
+ // Gracefully handle non-numbers.
+ if ( isNaN( minutes ) ) {
+ return 0;
+ } else {
+ return minutes;
+ }
+ }
+
+ function updateTimezoneSelection() {
+ var minuteDiff, localTime,
+ type = $tzSelect.val();
+
+ if ( type === 'other' ) {
+ // User specified time zone manually in <input>
+ // Grab data from the textbox, parse it.
+ minuteDiff = hoursToMinutes( $tzTextbox.val() );
+ } else {
+ // Time zone not manually specified by user
+ if ( type === 'guess' ) {
+ // Get browser timezone & fill it in
+ minuteDiff = -( new Date().getTimezoneOffset() );
+ $tzTextbox.val( minutesToHours( minuteDiff ) );
+ $tzSelect.val( 'other' );
+ $tzTextbox.prop( 'disabled', false );
+ } else {
+ // Grab data from the $tzSelect value
+ minuteDiff = parseInt( type.split( '|' )[ 1 ], 10 ) || 0;
+ $tzTextbox.val( minutesToHours( minuteDiff ) );
+ }
+
+ // Set defaultValue prop on the generated box so we don't trigger the
+ // unsaved preferences check
+ $tzTextbox.prop( 'defaultValue', $tzTextbox.val() );
+ }
+
+ // Determine local time from server time and minutes difference, for display.
+ localTime = servertime + minuteDiff;
+
+ // Bring time within the [0,1440) range.
+ localTime = ( ( localTime % 1440 ) + 1440 ) % 1440;
+
+ $localtimeHolder.text( mw.language.convertNumber( minutesToHours( localTime ) ) );
+ }
+
+ if ( $tzSelect.length && $tzTextbox.length ) {
+ $tzSelect.change( updateTimezoneSelection );
+ $tzTextbox.blur( updateTimezoneSelection );
+ updateTimezoneSelection();
+ }
+
+ } );
+}( mediaWiki, jQuery ) );
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8">
-<circle cx="4" cy="4" r="2"/>
+ <circle cx="4" cy="4" r="2"/>
</svg>
] )
);
}
+
+ /**
+ * @covers Xml::fieldset
+ */
+ public function testFieldset() {
+ $this->assertEquals(
+ "<fieldset>\n",
+ Xml::fieldset(),
+ 'Opening tag'
+ );
+ $this->assertEquals(
+ "<fieldset>\n",
+ Xml::fieldset( false ),
+ 'Opening tag (false means no legend)'
+ );
+ $this->assertEquals(
+ "<fieldset>\n",
+ Xml::fieldset( '' ),
+ 'Opening tag (empty string also means no legend)'
+ );
+ $this->assertEquals(
+ "<fieldset>\n<legend>Foo</legend>\n",
+ Xml::fieldset( 'Foo' ),
+ 'Opening tag with legend'
+ );
+ $this->assertEquals(
+ "<fieldset>\n<legend>Foo</legend>\nBar\n</fieldset>\n",
+ Xml::fieldset( 'Foo', 'Bar' ),
+ 'Entire element with legend'
+ );
+ $this->assertEquals(
+ "<fieldset>\n<legend>Foo</legend>\n",
+ Xml::fieldset( 'Foo', false ),
+ 'Opening tag with legend (false means no content and no closing tag)'
+ );
+ $this->assertEquals(
+ "<fieldset>\n<legend>Foo</legend>\n\n</fieldset>\n",
+ Xml::fieldset( 'Foo', '' ),
+ 'Entire element with legend but no content (empty string generates a closing tag)'
+ );
+ $this->assertEquals(
+ "<fieldset class=\"bar\">\n<legend>Foo</legend>\nBar\n</fieldset>\n",
+ Xml::fieldset( 'Foo', 'Bar', [ 'class' => 'bar' ] ),
+ 'Opening tag with legend and attributes'
+ );
+ $this->assertEquals(
+ "<fieldset class=\"bar\">\n<legend>Foo</legend>\n",
+ Xml::fieldset( 'Foo', false, [ 'class' => 'bar' ] ),
+ 'Entire element with legend and attributes'
+ );
+ }
}
// 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';
+ $svg = 'data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22UTF-8%22%3F%3E%0A'
+ . '%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%228%22 height='
+ . '%228%22%3E%0A%09%3Ccircle cx=%224%22 cy=%224%22 r=%222%22/%3E%0A%3C/svg%3E%0A';
// @codingStandardsIgnoreStart Generic.Files.LineLength
return [
[
'SVG files are embedded without base64 encoding and unnecessary IE 6 and 7 fallback',
'foo { /* @embed */ background: url(circle.svg); }',
- "foo { background: url($svg); }",
+ "foo { background: url(\"$svg\"); }",
],
[
'Two regular files in one rule',
return Maintenance::DB_ADMIN;
}
+ protected function addOption( $name, $description, $required = false,
+ $withArg = false, $shortName = false, $multiOccurrence = false
+ ) {
+ // ignore --quiet which does not really make sense for unit tests
+ if ( $name !== 'quiet' ) {
+ parent::addOption( $name, $description, $required, $withArg, $shortName, $multiOccurrence );
+ }
+ }
+
/**
* Force the format of elements in $_SERVER['argv']
* - Split args such as "wiki=enwiki" into two separate arg elements "wiki" and "enwiki"