LanguageConverter variant. This allows English-speaking developers
to develop and test LanguageConverter more easily. Pig Latin can be
enabled by setting $wgUsePigLatinVariant to true.
+* Added RecentChangesPurgeRows hook to allow extensions to purge data that
+ depends on the recentchanges table.
=== Languages updated in 1.30 ===
'RecentChange_save': Called at the end of RecentChange::save().
&$recentChange: RecentChange object
+'RecentChangesPurgeRows': Called when old recentchanges rows are purged, after
+deleting those rows but within the same transaction.
+$rows: The deleted rows as an array of recentchanges row objects (with up to
+ $wgUpdateRowsPerQuery items).
+
'RedirectSpecialArticleRedirectParams': Lets you alter the set of parameter
names such as "oldid" that are preserved when using redirecting special pages
such as Special:MyPage and Special:MyTalk.
*/
static function getAttribsRegex() {
if ( self::$attribsRegex === null ) {
- $attribFirst = '[:A-Z_a-z0-9]';
- $attrib = '[:A-Z_a-z-.0-9]';
+ $attribFirst = "[:_\p{L}\p{N}]";
+ $attrib = "[:_\.\-\p{L}\p{N}]";
$space = '[\x09\x0a\x0c\x0d\x20]';
self::$attribsRegex =
"/(?:^|$space)({$attribFirst}{$attrib}*)
| '([^']*)(?:'|\$)
| (((?!$space|>).)*)
)
- )?(?=$space|\$)/sx";
+ )?(?=$space|\$)/sxu";
}
return self::$attribsRegex;
}
$ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
do {
- $rcIds = $dbw->selectFieldValues( 'recentchanges',
- 'rc_id',
+ $rcIds = [];
+ $rows = [];
+ $res = $dbw->select( 'recentchanges',
+ RecentChange::selectFields(),
[ 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ],
__METHOD__,
[ 'LIMIT' => $wgUpdateRowsPerQuery ]
);
+ foreach ( $res as $row ) {
+ $rcIds[] = $row->rc_id;
+ $rows[] = $row;
+ }
if ( $rcIds ) {
$dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
+ Hooks::run( 'RecentChangesPurgeRows', [ $rows ] );
// There might be more, so try waiting for replica DBs
try {
$factory->commitAndWaitForReplication(
$this->logger->info( __METHOD__ . ": recognized file as video/x-matroska\n" );
return "video/x-matroska";
} elseif ( strncmp( $data, "webm", 4 ) == 0 ) {
- $this->logger->info( __METHOD__ . ": recognized file as video/webm\n" );
- return "video/webm";
+ // XXX HACK look for a video track, if we don't find it, this is an audio file
+ $videotrack = strpos( $head, "\x86\x85V_VP" );
+
+ if ( $videotrack ) {
+ // There is a video track, so this is a video file.
+ $this->logger->info( __METHOD__ . ": recognized file as video/webm\n" );
+ return "video/webm";
+ }
+
+ $this->logger->info( __METHOD__ . ": recognized file as audio/webm\n" );
+ return "audio/webm";
}
}
$this->logger->info( __METHOD__ . ": unknown EBML file\n" );
$suppress = '';
}
$checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
- $form = Html::openElement( 'form', [ 'method' => 'post',
- 'action' => $title->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ] ) .
- Html::openElement( 'fieldset', [ 'id' => 'mw-delete-table' ] ) .
- Html::element( 'legend', null, wfMessage( 'delete-legend' )->text() ) .
- Html::openElement( 'div', [ 'id' => 'mw-deleteconfirm-table' ] ) .
- Html::openElement( 'div', [ 'id' => 'wpDeleteReasonListRow' ] ) .
- Html::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
- ' ' .
- Xml::listDropDown(
- 'wpDeleteReasonList',
- wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
- wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
- '',
- 'wpReasonDropDown',
- 1
- ) .
- Html::closeElement( 'div' ) .
- Html::openElement( 'div', [ 'id' => 'wpDeleteReasonRow' ] ) .
- Html::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
- ' ' .
- Html::input( 'wpReason', $reason, 'text', [
- 'size' => '60',
- 'maxlength' => '255',
- 'tabindex' => '2',
- 'id' => 'wpReason',
- 'class' => 'mw-ui-input-inline',
- 'autofocus'
- ] ) .
- Html::closeElement( 'div' );
-
- # Disallow watching if user is not logged in
- if ( $user->isLoggedIn() ) {
- $form .=
- Xml::checkLabel( wfMessage( 'watchthis' )->text(),
- 'wpWatch', 'wpWatch', $checkWatch, [ 'tabindex' => '3' ] );
- }
-
- $form .=
- Html::openElement( 'div' ) .
- $suppress .
- Xml::submitButton( wfMessage( 'deletepage' )->text(),
- [
- 'name' => 'wpConfirmB',
- 'id' => 'wpConfirmB',
- 'tabindex' => '5',
- 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button mw-ui-destructive' : '',
- ]
- ) .
- Html::closeElement( 'div' ) .
- Html::closeElement( 'div' ) .
- Xml::closeElement( 'fieldset' ) .
- Html::hidden(
- 'wpEditToken',
- $user->getEditToken( [ 'delete', $title->getPrefixedText() ] )
- ) .
- Xml::closeElement( 'form' );
-
- if ( $user->isAllowed( 'editinterface' ) ) {
- $link = Linker::linkKnown(
- $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
- wfMessage( 'delete-edit-reasonlist' )->escaped(),
- [],
- [ 'action' => 'edit' ]
- );
- $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
+
+ $outputPage->enableOOUI();
+
+ $options = [];
+ $options[] = [
+ 'data' => 'other',
+ 'label' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text(),
+ ];
+ $list = $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text();
+ foreach ( explode( "\n", $list ) as $option ) {
+ $value = trim( $option );
+ if ( $value == '' ) {
+ continue;
+ } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
+ $options[] = [ 'optgroup' => trim( substr( $value, 1 ) ) ];
+ } elseif ( substr( $value, 0, 2 ) == '**' ) {
+ $options[] = [ 'data' => trim( substr( $value, 2 ) ) ];
+ } else {
+ $options[] = [ 'data' => trim( $value ) ];
}
+ }
+
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\DropdownInputWidget( [
+ 'name' => 'wpDeleteReasonList',
+ 'inputId' => 'wpDeleteReasonList',
+ 'tabIndex' => 1,
+ 'infusable' => true,
+ 'value' => '',
+ 'options' => $options
+ ] ),
+ [
+ 'label' => $ctx->msg( 'deletecomment' )->text(),
+ 'align' => 'top',
+ ]
+ );
- $outputPage->addHTML( $form );
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( [
+ 'name' => 'wpReason',
+ 'inputId' => 'wpReason',
+ 'tabIndex' => 2,
+ 'maxLength' => 255,
+ 'infusable' => true,
+ 'value' => $reason,
+ 'autofocus' => true,
+ ] ),
+ [
+ 'label' => $ctx->msg( 'deleteotherreason' )->text(),
+ 'align' => 'top',
+ ]
+ );
+
+ if ( $user->isLoggedIn() ) {
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( [
+ 'name' => 'wpWatch',
+ 'inputId' => 'wpWatch',
+ 'tabIndex' => 3,
+ 'selected' => $checkWatch,
+ ] ),
+ [
+ 'label' => $ctx->msg( 'watchthis' )->text(),
+ 'align' => 'inline',
+ 'infusable' => true,
+ ]
+ );
+ }
+
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\ButtonInputWidget( [
+ 'name' => 'wpConfirmB',
+ 'inputId' => 'wpConfirmB',
+ 'tabIndex' => 5,
+ 'value' => $ctx->msg( 'deletepage' )->text(),
+ 'label' => $ctx->msg( 'deletepage' )->text(),
+ 'flags' => [ 'primary', 'destructive' ],
+ 'type' => 'submit',
+ ] ),
+ [
+ 'align' => 'top',
+ ]
+ );
+
+ $fieldset = new OOUI\FieldsetLayout( [
+ 'label' => $ctx->msg( 'delete-legend' )->text(),
+ 'id' => 'mw-delete-table',
+ 'items' => $fields,
+ ] );
+
+ $form = new OOUI\FormLayout( [
+ 'method' => 'post',
+ 'action' => $title->getLocalURL( 'action=delete' ),
+ 'id' => 'deleteconfirm',
+ ] );
+ $form->appendContent(
+ $fieldset,
+ new OOUI\HtmlSnippet(
+ Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
+ )
+ );
+
+ $outputPage->addHTML(
+ new OOUI\PanelLayout( [
+ 'classes' => [ 'deletepage-wrapper' ],
+ 'expanded' => false,
+ 'padded' => true,
+ 'framed' => true,
+ 'content' => $form,
+ ] )
+ );
+
+ if ( $user->isAllowed( 'editinterface' ) ) {
+ $link = Linker::linkKnown(
+ $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
+ wfMessage( 'delete-edit-reasonlist' )->escaped(),
+ [],
+ [ 'action' => 'edit' ]
+ );
+ $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
+ }
$deleteLogPage = new LogPage( 'delete' );
$outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
function showSearchForm() {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'undelete-search-title' ) );
- $fuzzySearch = $this->getRequest()->getVal( "fuzzy", false );
- $out->addHTML(
- Xml::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] ) .
- Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
+ $fuzzySearch = $this->getRequest()->getVal( 'fuzzy', false );
+
+ $out->enableOOUI();
+
+ $fields[] = new OOUI\ActionFieldLayout(
+ new OOUI\TextInputWidget( [
+ 'name' => 'prefix',
+ 'inputId' => 'prefix',
+ 'infusable' => true,
+ 'value' => $this->mSearchPrefix,
+ 'autofocus' => true,
+ ] ),
+ new OOUI\ButtonInputWidget( [
+ 'label' => $this->msg( 'undelete-search-submit' )->text(),
+ 'flags' => [ 'primary', 'progressive' ],
+ 'inputId' => 'searchUndelete',
+ 'type' => 'submit',
+ ] ),
+ [
+ 'label' => new OOUI\HtmlSnippet(
+ $this->msg(
+ $fuzzySearch ? 'undelete-search-full' : 'undelete-search-prefix'
+ )->parse()
+ ),
+ 'align' => 'left',
+ ]
+ );
+
+ $fieldset = new OOUI\FieldsetLayout( [
+ 'label' => $this->msg( 'undelete-search-box' )->text(),
+ 'items' => $fields,
+ ] );
+
+ $form = new OOUI\FormLayout( [
+ 'method' => 'get',
+ 'action' => wfScript(),
+ ] );
+
+ $form->appendContent(
+ $fieldset,
+ new OOUI\HtmlSnippet(
Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
- Html::hidden( 'fuzzy', $this->getRequest()->getVal( 'fuzzy' ) ) .
- Html::rawElement(
- 'label',
- [ 'for' => 'prefix' ],
- $this->msg( $fuzzySearch ? 'undelete-search-full' : 'undelete-search-prefix' )
- ->parse()
- ) .
- Xml::input(
- 'prefix',
- 20,
- $this->mSearchPrefix,
- [ 'id' => 'prefix', 'autofocus' => '' ]
- ) .
- ' ' .
- Xml::submitButton(
- $this->msg( 'undelete-search-submit' )->text(),
- [ 'id' => 'searchUndelete' ]
- ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
+ Html::hidden( 'fuzzy', $this->getRequest()->getVal( 'fuzzy' ) )
+ )
+ );
+
+ $out->addHTML(
+ new OOUI\PanelLayout( [
+ 'expanded' => false,
+ 'padded' => true,
+ 'framed' => true,
+ 'content' => $form,
+ ] )
);
# List undeletable articles
// Make sure this uses the interface direction, not the content direction
direction: ltr;
border-bottom-right-radius: 0;
+ height: 2.5em;
}
&.oo-ui-widget-enabled .oo-ui-tagMultiselectWidget-handle {
width: 1em;
&-widget.oo-ui-widget {
+ border: 1px solid #a2a9b1;
+ border-left-width: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 0;
+
display: block;
text-align: right;
+ height: 2.5em;
+ box-sizing: border-box;
- // Override OOUI rules
- &.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:first-child a.oo-ui-buttonElement-button,
- .oo-ui-buttonOptionWidget a.oo-ui-buttonElement-button {
- border-radius: 0;
- border-left: 0;
- }
-
- &.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:last-child a.oo-ui-buttonElement-button {
- border-bottom-right-radius: 2px;
+ .oo-ui-buttonElement-frameless.oo-ui-iconElement:first-child {
+ margin-left: 0;
}
}
classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-views-select-widget' ],
items: [
new OO.ui.ButtonOptionWidget( {
+ framed: false,
data: 'namespaces',
icon: 'article',
title: mw.msg( 'rcfilters-view-namespaces-tooltip' )
} ),
new OO.ui.ButtonOptionWidget( {
+ framed: false,
data: 'tags',
icon: 'tag',
title: mw.msg( 'rcfilters-view-tags-tooltip' )
break;
case 'text':
- widget = new OO.ui.TextInputWidget( {
- multiline: true,
+ widget = new OO.ui.MultilineTextInputWidget( {
required: Util.apiBool( pi.required )
} );
widget.paramInfo = pi;
new OO.ui.MenuOptionWidget( {
label: Util.parseMsg( 'apisandbox-request-format-json-label' ),
data: new OO.ui.FieldLayout(
- jsonInput = new OO.ui.TextInputWidget( {
+ jsonInput = new OO.ui.MultilineTextInputWidget( {
classes: [ 'mw-apisandbox-textInputCode' ],
readOnly: true,
- multiline: true,
autosize: true,
maxRows: 6,
value: JSON.stringify( displayParams, null, '\t' )
classes: [ 'mw-feedbackDialog-welcome-message' ]
} );
this.feedbackSubjectInput = new OO.ui.TextInputWidget( {
- indicator: 'required',
- multiline: false
+ indicator: 'required'
} );
- this.feedbackMessageInput = new OO.ui.TextInputWidget( {
- autosize: true,
- multiline: true
+ this.feedbackMessageInput = new OO.ui.MultilineTextInputWidget( {
+ autosize: true
} );
feedbackSubjectFieldLayout = new OO.ui.FieldLayout( this.feedbackSubjectInput, {
label: mw.msg( 'feedback-subject' )
public static function provideTagAttributesToDecode() {
return [
[ [ 'foo' => 'bar' ], 'foo=bar', 'Unquoted attribute' ],
+ [ [ 'עברית' => 'bar' ], 'עברית=bar', 'Non-Latin attribute' ],
+ [ [ '६' => 'bar' ], '६=bar', 'Devanagari number' ],
+ [ [ '搭𨋢' => 'bar' ], '搭𨋢=bar', 'Non-BMP character' ],
+ [ [], 'ńgh=bar', 'Combining accent is not allowed' ],
[ [ 'foo' => 'bar' ], ' foo = bar ', 'Spaced attribute' ],
[ [ 'foo' => 'bar' ], 'foo="bar"', 'Double-quoted attribute' ],
[ [ 'foo' => 'bar' ], 'foo=\'bar\'', 'Single-quoted attribute' ],